Fountain (Jet d'Eau) API
One number per column (the big dot), the real range around it (the fountain, from low to high) and the measurements themselves (the small dots), all on one y-axis. Categorical x = snapshot; temporal or numeric x = trend. See the Fountain demo and its reading key.
Import
import "@michi-vz/wc/fountain-chart";
// <michi-vz-fountain-chart> is now definedimport { mountFountainChart } from "@michi-vz/core";
const chart = mountFountainChart(el, props);import { FountainChart } from "@michi-vz/react/fountain-chart";import { FountainChart } from "@michi-vz/vue/fountain-chart";import { fountainChart } from "@michi-vz/svelte/fountain-chart";import { bindChart, applyFountainChartProps } from "@michi-vz/angular/fountain-chart";
// needs CUSTOM_ELEMENTS_SCHEMA on the component that hosts <michi-vz-fountain-chart>Props
| Prop | Type | Default | Description |
|---|---|---|---|
timeline | boolean | TimelinePeriodConfig | - | Opt-in "play through years" (cumulative): the marks draw UP TO the active period and extend as the timeline steps; `interpolate` sweeps smoothly between years, `false` jump-cuts. Headless controller via `chart.timeline()` plus the built-in play button + scrubber. Off by default; wins over `progressiveDraw` when both are set. The reveal always shows the whole active jet, fountain and small dots included, and a jet's value labels appear with it; undrawn jets cannot be hovered. Trend mode only. Not drawn by the "webgpu" renderer (an ignored-option warning). |
progressiveDraw | boolean | ProgressiveDrawConfig | - | Opt-in reveal animation: wipes the marks in left to right on mount (a clip reveal; axes and titles stay put). `true` uses defaults (1200 ms, easeInOutCubic); a config tunes durationMs, easing, autoplay, and replayOnUpdate (`tipLabel` is LineChart-only and ignored here). Off by default; respects prefers-reduced-motion (renders fully drawn instantly) and `replay()` re-runs it. Jets the wipe has not reached cannot be hovered. Not drawn by the "webgpu" renderer (an ignored-option warning). |
dataSet* | FountainDataItem[] | - | Array of jets; each item renders one fountain |
title | string | - | Optional chart title rendered above the plot |
xAxisDataType | FountainXAxisType | - | How the x-axis is parsed: a temporal/numeric type renders TREND mode (jets at their dates, the x range inset by half a column on each side, one tick per period); "band" (or omitted) renders SNAPSHOT mode (one column per label) |
yAxisDomain | [number, number] | - | Explicit [min, max] for the value (y) axis, used as given (not rounded). The auto domain includes 0, every value, low, high and reference line, plus 10% headroom, rounded to nice ticks. Data outside a user domain is clamped to the plot edge and small dots outside it are not drawn (an `out-of-domain` warning). |
xAxisFormat | (d: number | string) => string | - | Formats an x tick value into its display label |
yAxisFormat | (d: number | string) => string | - | Formats a y value: the tick labels and the numbers in the value labels and the tooltip (default: the locale's number format) |
ticks | number | 5 | Approximate number of axis ticks to generate (default 5) |
tickValues | Array<number | Date> | - | Explicit x tick values, overriding the per-period ticks (trend mode) |
showRange | boolean | true | Draw the fountain: the range [low, high] as a bell (default true). `false` draws the stem and the big dot only; the small dots need the fountain to sit in, so they hide too. |
showSamples | boolean | true | Draw the small dots, one per sample, when items carry `samples` (default true) |
showValueLabels | boolean | true | Print value labels under each x label (default true): bold "usual 30", then "<low word> 22" and "<high word> 55" (see `endLabels`), "only N <sampleWord>" under 10 samples, and per reference line with a `goodSide` a bold "17 of 20" plus its `countLabel`. The chart reserves bottom margin for them. On narrow columns the text shrinks, then the end lines go, before a label would overlap a neighbour; when even the usual line cannot fit, when the x labels have to tilt, or where two jets share a column, the value labels are left out (a `layout-overflow` warning). |
drift | boolean | false | The Geneva look: the top of every fountain leans downwind, the same way for every jet, so it means nothing (default false: readers found it confusing) |
yAxisTitle | string | - | Title drawn rotated beside the y-axis, e.g. "minutes (higher = slower)" |
endLabels | [string, string] | - | Words for the low and high ends in the value labels and tooltip (default ["lowest", "highest"]; e.g. ["best", "worst"], ["cheapest", "dearest"]) |
referenceLines | FountainReferenceLine[] | - | Horizontal reference lines (a promise, a limit, a budget). With `goodSide`, each jet with samples counts its small dots on the good side. |
labels | FountainLabels | - | UI words for localisation (English defaults "usual", "of", "only", "forecast") |
readingGuide | boolean | string | - | A reading guide under the plot (default false), on one line when it fits, else wrapped between its rules (at each " · "), never inside one. `true` prints the default ("Small dot = one measurement · Big dot = the usual one · Dots close together = steady · Tall fountain = changes a lot · Few dots = just a guess") with only the rules for marks the chart draws: no small-dot rules without small dots (no samples, showSamples or showRange off, forecasts only), no "Tall fountain" rule without a fountain. A string replaces it and is printed as given. |
sampleWord | string | - | PLURAL noun for the samples in the value labels and tooltip, e.g. "days", "orders" (default "measurements"). Plural on purpose: there is no pluralising logic, and one string is easy to localise. |
showTrendLine | boolean | - | Draw a dashed grey line through the big dots, left to right (default true in trend mode with one series; false with several series, where one line would zig-zag between them, and in snapshot mode; set it true to join an ordered sequence of categories, e.g. hours) |
tooltipFormatter | (d: FountainDataItem & { value: number }, jet?: FountainTooltipJet) => string | - | Returns custom tooltip HTML for a hovered or pinned jet (sanitized before it is inserted). Default: the label (with its period in trend mode), "usual 30", "<low word> 22 · <high word> 55", "20 <sampleWord>" and a count per reference line with a goodSide, numbers formatted like the y-axis. Click or tap pins the tooltip; clicking the pinned jet or empty space, or Escape, unpins. The first argument is the item with its resolved `value` (the median of its samples when it gives none), so `d.value` is always a number; a formatter typed `(d: FountainDataItem) => string` still fits. The second is the jet as drawn (its range, samples, reference counts, period and the default lines), so a custom tooltip needs no re-derivation. |
isLoading | boolean | - | Show the loading overlay; with nothing drawn yet the axes and marks wait for data, while a refetch keeps the stale jets on screen. |
isNodata | boolean | ((dataSet: FountainDataItem[] | null | undefined) => boolean) | - | No-data override: boolean, or a predicate on the data; default = an empty dataSet. With no data the chart draws the no-data overlay instead of axes and marks. |
noDataLabel | string | - | Text for the vanilla default no-data overlay (ignored when suppressed). |
suppressDefaultOverlay | boolean | - | A framework wrapper sets this to render its OWN loading/no-data node instead. |
onHighlightItem | (labels: string[]) => void | - | Called when the hovered, pinned or cleared label changes (only on a change: moving within one jet does not re-fire; leaving every jet fires []) |
style | "jet" | "plume" | - | Deprecated ignored since core 1.29; emits an ignored-option warning. There is one look now: stem, fountain, small dots, big dot. |
frothLayers | number | - | Deprecated ignored since core 1.29; emits an ignored-option warning |
bloomExponent | number | - | Deprecated ignored since core 1.29; emits an ignored-option warning |
stemFraction | number | - | Deprecated ignored since core 1.29; emits an ignored-option warning |
showDroplets | boolean | - | Deprecated ignored since core 1.29; emits an ignored-option warning |
showMist | boolean | - | Deprecated ignored since core 1.29; emits an ignored-option warning |
Common props - shared by every chart (14)
| Prop | Type | Default | Description |
|---|---|---|---|
width | number | 900 | Chart width in pixels |
height | number | 480 | Chart height in pixels |
margin | Margin | { top: 50, right: 40, bottom: 50, left: 60 } | Inner margins (top/right/bottom/left, in px); default 8 (36 top with a title) |
colors | string[] | - | Categorical palette for rings without an explicit colour or colorsMapping entry |
colorsMapping | Record<string, string> | - | Explicit label -> colour map; takes precedence over the palette and per-item colours |
highlightItems | string[] | - | Labels to emphasise (active styling); others keep their base opacity |
disabledItems | string[] | - | Labels to hide entirely (ring + track removed; remaining rings keep their radii order) |
renderer | "svg" | "canvas" | "webgpu" | "svg" | Render as inline SVG (default) or to a canvas / WebGPU layer; getContext() is identical either way |
locale | string | - | BCP-47 locale used for number formatting |
skipColorMappingDispatch | boolean | false | External-CSS mode: unmapped labels resolve to transparent and onColorMappingGenerated is not emitted, so arc colours come from your CSS via the data-label-safe contract |
enableTransitions | boolean | true | Animate opacity/arc changes with CSS transitions (SVG renderer; default true) |
onColorMappingGenerated | (mapping: Record<string, string>) => void | - | Called with the resolved label -> colour map after the chart assigns colours |
onChartDataProcessed | (context: ChartContext) => void | - | Called with the renderer-agnostic ChartContext whenever the data is (re)processed |
onDataWarning | (warnings: DataWarning[]) => void | - | Called with any non-fatal data warnings (out-of-range values, duplicate labels, ...) |
Two modes, one data shape
Set xAxisDataType: "band" (or omit it) for snapshot mode: one column per label. Give a temporal or numeric xAxisDataType plus a date on each item for trend mode: the jets sit along the x-axis, and with one series a dashed line joins the big dots. A forecast: true item gets a dashed stem and outline, a lighter fill and a hollow big dot, with no small dots.
New in core 1.29
| Prop | Type | Default | What it does |
|---|---|---|---|
showRange | boolean | true | Draw the fountain, the range [low, high]. false draws the stem and the big dot only; the small dots hide too. |
showSamples | boolean | true | Draw one small dot per sample, when items carry samples. |
showValueLabels | boolean | true | Print under each x label: bold "usual 30", "<low word> 22", "<high word> 55", "only N <sampleWord>" under 10 samples, and per reference line with a goodSide a bold "17 of 20" plus its countLabel. |
drift | boolean | false | The Geneva look: the top of every fountain bends the same way. It carries no data. |
yAxisTitle | string | none | Title drawn rotated beside the y-axis, e.g. "minutes (higher = slower)". |
endLabels | [string, string] | ["lowest", "highest"] | Words for the low and the high end in the value labels and the tooltip. |
referenceLines | FountainReferenceLine[] | none | Dashed lines in the theme's attention colour, labelled at the right end. See below. |
labels | FountainLabels | English | The chart's other words, for localisation. See below. |
readingGuide | boolean | string | false | A key under the plot, wrapped between its rules (at each " · ") when it does not fit on one line. true prints the default, naming only the marks the chart draws (no small-dot rules without small dots, no "Tall fountain" without a fountain); a string replaces it. |
sampleWord | string | "measurements" | Plural noun for the samples ("days", "orders"). Plural on purpose: there is no pluralising logic. |
showTrendLine now defaults per mode: true in trend mode with one series; false with several series, where one line would zig-zag between them, and in snapshot mode (set it true to join an ordered row of categories).
Data item: FountainDataItem
| Field | Type | What it is |
|---|---|---|
label | string | The column (snapshot) or the series name (trend). Drives the colour and the data-label hook. |
code | string | Optional stable id carried into the context; not displayed. |
value | number | The big dot. Optional when samples are given: then their median. With no finite value and no samples the jet is skipped. |
low | number | The base of the fountain. |
high | number | The top of the fountain. |
spread | number | Shorthand for an even range: low = value - spread, high = value + spread. |
samples | number[] | Real measurements, one small dot each, drawn at their exact height inside the fountain. |
forecast | boolean | A predicted period: dashed stem and outline, lighter fill, hollow big dot, no small dots and no counts. |
color | string | Per-item colour. Resolution per jet: colorsMapping[label], then color, then the label's palette slot. |
date | number | string | The x position in trend mode; an item without a usable date is skipped there. |
predicted | boolean | Deprecated: use forecast. Still honoured. |
certainty | boolean | Deprecated: certainty: false is forecast: true. Still honoured. |
density | number | Deprecated and ignored (an ignored-option warning). |
lean | number | Deprecated and ignored (an ignored-option warning). |
The range comes from the first of: low/high, then spread, then the lowest and highest sample; with none, the jet has no fountain. A missing end falls back along the same chain. Samples outside an explicit range, and a value outside the range, widen the range and send a warning. Negative values are allowed: the y domain includes 0 and every low, and the stem runs down from the baseline.
Reference line: FountainReferenceLine
| Field | Type | What it is |
|---|---|---|
value | number | Where the line sits, in y units. Always inside the automatic y domain. |
label | string | Printed at the right end of the line (wrapped; the chart reserves right margin). |
goodSide | "below" | "above" | Which side is good. When set, every jet with samples counts them: "below" counts the samples at or below the line, "above" those at or above it. |
countLabel | string | Words after the count, e.g. "on time", "passed". Default "below the line" or "above the line". |
Words: FountainLabels
| Field | Default | Where it shows |
|---|---|---|
usual | "usual" | Before the big dot's value: "usual 30". |
of | "of" | Between a count and its total: "17 of 20". |
only | "only" | Before a small sample count: "only 5 days". |
forecast | "forecast" | After a forecast jet's x label and in its tooltip: "Fri (forecast)". |
Theme tokens
The chart reads these CSS custom properties from its host element (or any ancestor), in every renderer:
| Token | Default | What it colours |
|---|---|---|
--michi-vz-surface | #fff | The background the chart sits on: the thin ring round each small dot and the ring round a big dot, which keep them apart from the marks under them. On a dark theme, set it to your page's background. |
--michi-vz-attention | #c0392b | The reference lines, their labels and the counts under the columns. |
--michi-vz-lake | #9cc3dd | The band at 0 (the lake). |
--michi-vz-ink | currentColor | The trend line, the bold "usual 30" line and the title. |
--michi-vz-muted | #666 | The other value labels, the y-axis title, the reading guide and the axis labels. |
--michi-vz-grid | lightgray | The thin rule above the reading guide. |
--michi-vz-font-family, --michi-vz-font-size | inherited, 12px | Every word the chart prints. |
A forecast's hollow big dot is a ring with nothing painted inside (the marks under it are cut away), so it stays hollow on any background without a token.
/* The charts on a dark page */
.dark .charts {
--michi-vz-surface: #1b1b1f;
--michi-vz-ink: #e3e3e3;
--michi-vz-muted: #a0a0a0;
}Events
The web component dispatches these bubbling CustomEvents (the engine exposes the same via the on* callbacks in the table above):
| Event | Detail | Fires when |
|---|---|---|
michi-vz:highlight | string[] | the hovered jet changes (its label) |
michi-vz:colormapping | Record<string, string> | a colour mapping is generated |
michi-vz:dataprocessed | ChartContext | data is (re)processed |
michi-vz:datawarning | DataWarning[] | input warnings are detected (see Warnings) |
getContext()
mountFountainChart(el, props).getContext() returns a renderer-agnostic FountainChartContext:
mode:"snapshot"for a categorical (band) x,"trend"for a temporal or numeric x.xAxis:{ type, domain }, the column labels in snapshot mode or[min, max]in trend mode.yAxis:{ domain }.jets: one entry per drawn jet (in x order in trend mode):
| Field | What it is |
|---|---|
label, code, color | The jet's label, its optional id and its resolved colour. |
value | The big dot. |
low, high | The base and the top of the fountain; null without a range. |
range | high - low; null without a range. |
rangeRatio | range / |value|: how big the range is next to the number. null when the value is 0 or there is no range. |
sampleCount | Number of small dots. |
referenceCounts | One { value, goodSide, count, total, countLabel } per reference line with a goodSide; [] for a forecast or a jet without samples. |
predicted | true for a forecast jet. |
xPosition | The raw date in trend mode, null in snapshot mode. |
spread | Deprecated: (high - low) / 2, 0 without a range. Use range. |
spreadRatio | Deprecated: spread / |value|, 0 when not computable. Use rangeRatio. |
upperBound | Deprecated: high (or the value without a range). Use high. |
lean | Deprecated: always null. |
stats:jetCount: number of drawn jets.tallest:{ label, value }of the largest value, ornull.widestRange:{ label, range }of the widest range, ornullwhen no jet has one.frothiest: deprecated.{ label, spreadRatio }of the jet with the largestrangeRatio. UsewidestRangeorjets[].rangeRatio.trendSlope: least-squares slope of the values per period (trend mode, one series); otherwisenull.valueRange:[min, max]of the values, ornull.predictedCount: number of forecast jets.
legendData: every label of the wholedataSetwith its colour, in first-seen order; disabled labels stay, flaggeddisabled: true. In trend mode only when there is more than one series.summary: one sentence in plain words, e.g.Fountain chart "How long is my commute, really?" with 4 jets. Highest usual value: Bus at 40. Widest range: Car, from 22 to 55.a11yTable: headersLabel,Usual, the twoendLabels,Samples, and one column per reference line with agoodSide(headed by itscountLabel, cells like"17 of 20"); trend mode addsPeriodfirst. A forecast jet's label gets "(forecast)" after it: its trend row reads["Year 4", "Battery (forecast)", …].
See LLM context for how to use the context in prompts and reports.
Warnings
onDataWarning (and the michi-vz:datawarning event) receives DataWarning[], each { type, message, label? }. The chart never redraws data silently: every repair is reported.
type | When | What the chart does |
|---|---|---|
non-finite-value | An item has no finite value and no samples, or some samples are not finite. | Skips the jet (it stays out of the stats), or drops those samples. A missing value with samples uses their median. |
range-excludes-value | low/high (or spread) leave the value out. | Widens the range to include the value. |
sample-outside-range | A sample lies outside an explicit range. | Widens the range to include it. |
inverted-range | low is above high, or spread is negative. | Swaps the ends (uses the spread's size). |
missing-date | Trend mode, but the item has no usable date. | Skips the item (the chart stays in trend mode). |
duplicate-date | Two jets share one date in trend mode. | Draws them on top of each other. |
duplicate-label | A label repeats in snapshot mode. | Its jets share one column. |
out-of-domain | A value, range end or sample is outside a user yAxisDomain, or a reference line is. | Clamps the drawing to the plot; small dots and lines outside it are not drawn. |
ignored-option | A removed prop is set, or an item carries density or lean. | Ignores it. |
empty-dataset | The dataSet is empty. | Shows the no-data overlay ("No data available", your noDataLabel, or your own with suppressDefaultOverlay) instead of axes and marks. isNodata: false draws the empty axes. |
layout-overflow | Each jet gets under 24 px, or the value labels do not fit (narrow columns, rotated x labels, jets sharing a column, or a chart too short for them). | Draws the jets anyway and leaves out the value labels that do not fit, the end words first. Widen the chart, show fewer jets or aggregate. |
Deprecations
All of these still work in core 1.29 and will be removed in a later release. See Migrating from core 1.28.
- Props, ignored with an
ignored-optionwarning:style,frothLayers,bloomExponent,stemFraction,showDroplets,showMist. On the web component,fountainStyle(fountain-style). - Item fields:
predictedandcertaintyare honoured; useforecast.densityandleanare ignored with a warning. - Context fields:
jets[].spread,jets[].spreadRatio,jets[].upperBoundandjets[].lean(alwaysnull);stats.frothiest. Userange,rangeRatio,highandstats.widestRange.
Source
Props are typed as FountainChartProps in @michi-vz/core.
