GrChartLine
Overview
When to take it
- how a quantity changed — revenue by month, load by hour, the balance by day: a line shows the course rather than individual measurements;
- several series side by side — the legend toggles the series, and the tooltip shows all of the values at one abscissa;
- there are gaps in the series —
nullbreaks the line rather than substituting a zero;gapsdraws a bridge across a gap when the readability of the trend matters more; - there are thousands of points —
decimate: 'auto'(the default) reduces the drawing to two vertices per pixel, while the cursor, the keyboard and the hidden table keep knowing the full series. The details —../model.md, the section “Decimation is a projection, not the data”.
When to take something else
| Need | Take |
|---|---|
| Show the whole and the contribution of its parts | GrChartArea |
| Compare quantities across categories | GrChartBar |
| Show what one whole consists of | GrChartPie |
| Compare the shape of a profile across several axes | GrChartRadar |
| A trend in a table cell, without axes and a frame | GrSparkline |
| One number large, with a trend | GrStatistic |
Examples 4 each with a live demo and its code
A month of latency, and the hour that broke it
720 points a series on a time scale: the path is thinned to the width, while the cursor and the hidden table still know every hour.
v-model:x-windowis the visible window — the presets set it,zoom="brush"lets the reader drag their own, and the axis switches from days to hours when the window gets short. The SLO is areferencesline, not a series.Checkout API · latencyLast 30 days · drag over the chart to zoom inChart data X p95 p50 Sep 7, 2026, 12:00 AM 153 58 Sep 7, 2026, 07:00 AM 131 66 Sep 7, 2026, 01:00 PM 232 80 Sep 7, 2026, 10:00 PM 147 66 Sep 8, 2026, 06:00 AM 146 66 Sep 8, 2026, 11:00 AM 228 80 Sep 8, 2026, 05:00 PM 229 81 Sep 8, 2026, 07:00 PM 169 77 Sep 9, 2026, 06:00 AM 138 64 Sep 9, 2026, 11:00 AM 218 78 Sep 9, 2026, 02:00 PM 254 82 Sep 9, 2026, 10:00 PM 145 64 Sep 10, 2026, 07:00 AM 152 59 Sep 10, 2026, 01:00 PM 239 91 Sep 10, 2026, 03:00 PM 244 84 Sep 10, 2026, 09:00 PM 165 57 Sep 11, 2026, 07:00 AM 139 68 Sep 11, 2026, 12:00 PM 239 79 Sep 11, 2026, 05:00 PM 234 81 Sep 11, 2026, 09:00 PM 138 62 Sep 12, 2026, 07:00 AM 128 66 Sep 12, 2026, 10:00 AM 176 75 Sep 12, 2026, 02:00 PM 206 70 Sep 12, 2026, 10:00 PM 127 58 Sep 13, 2026, 06:00 AM 128 59 Sep 13, 2026, 01:00 PM 207 70 Sep 13, 2026, 03:00 PM 165 76 Sep 14, 2026, 12:00 AM 139 63 Sep 14, 2026, 06:00 AM 135 61 Sep 14, 2026, 01:00 PM 249 80 Sep 14, 2026, 07:00 PM 199 66 Sep 14, 2026, 10:00 PM 128 66 Sep 15, 2026, 07:00 AM 134 61 Sep 15, 2026, 01:00 PM 249 86 Sep 15, 2026, 04:00 PM 244 80 Sep 16, 2026, 12:00 AM 133 61 Sep 16, 2026, 07:00 AM 137 64 Sep 16, 2026, 12:00 PM 243 84 Sep 16, 2026, 09:00 PM 138 68 Sep 16, 2026, 11:00 PM 172 60 Sep 17, 2026, 05:00 AM 133 65 Sep 17, 2026, 11:00 AM 231 80 Sep 17, 2026, 09:00 PM 148 56 Sep 18, 2026, 02:00 AM 138 68 Sep 18, 2026, 08:00 AM 151 67 Sep 18, 2026, 02:00 PM 253 88 Sep 18, 2026, 09:00 PM 150 58 Sep 19, 2026, 01:00 AM 170 64 Sep 19, 2026, 05:00 AM 136 68 Sep 19, 2026, 11:00 AM 200 73 Sep 19, 2026, 07:00 PM 154 73 Sep 20, 2026, 04:00 AM 129 65 Sep 20, 2026, 05:00 AM 164 62 Sep 20, 2026, 02:00 PM 204 72 Sep 20, 2026, 10:00 PM 137 67 Sep 21, 2026, 04:00 AM 128 64 Sep 21, 2026, 10:00 AM 218 77 Sep 21, 2026, 04:00 PM 240 84 Sep 21, 2026, 10:00 PM 128 60 Sep 22, 2026, 12:00 AM 164 63 Sep 22, 2026, 06:00 AM 150 67 Sep 22, 2026, 01:00 PM 250 81 Sep 22, 2026, 09:00 PM 148 66 Sep 23, 2026, 12:00 AM 138 69 Sep 23, 2026, 07:00 AM 135 68 Sep 23, 2026, 03:00 PM 252 88 Sep 23, 2026, 09:00 PM 142 63 Sep 24, 2026, 04:00 AM 135 61 Sep 24, 2026, 07:00 AM 629 177 Sep 24, 2026, 12:00 PM 239 77 Sep 24, 2026, 09:00 PM 128 59 Sep 25, 2026, 05:00 AM 129 59 Sep 25, 2026, 12:00 PM 238 79 Sep 25, 2026, 04:00 PM 248 87 Sep 25, 2026, 11:00 PM 133 67 Sep 26, 2026, 04:00 AM 169 59 Sep 26, 2026, 07:00 AM 135 68 Sep 26, 2026, 01:00 PM 209 71 Sep 26, 2026, 09:00 PM 139 64 Sep 27, 2026, 06:00 AM 137 62 Sep 27, 2026, 12:00 PM 202 67 Sep 27, 2026, 02:00 PM 171 71 Sep 27, 2026, 10:00 PM 135 62 Sep 28, 2026, 07:00 AM 135 61 Sep 28, 2026, 01:00 PM 240 91 Sep 28, 2026, 03:00 PM 247 80 Sep 28, 2026, 08:00 PM 148 66 Sep 29, 2026, 07:00 AM 148 67 Sep 29, 2026, 11:00 AM 231 77 Sep 29, 2026, 02:00 PM 257 89 Sep 29, 2026, 10:00 PM 139 67 Sep 30, 2026, 07:00 AM 145 65 Sep 30, 2026, 11:00 AM 230 76 Sep 30, 2026, 03:00 PM 247 81 Sep 30, 2026, 10:00 PM 141 68 Oct 1, 2026, 06:00 AM 134 66 Oct 1, 2026, 01:00 PM 235 81 Oct 1, 2026, 08:00 PM 149 72 Oct 1, 2026, 09:00 PM 172 64 Oct 2, 2026, 07:00 AM 135 62 Oct 2, 2026, 11:00 AM 230 81 Oct 2, 2026, 03:00 PM 250 82 Oct 2, 2026, 09:00 PM 128 65 Oct 3, 2026, 08:00 AM 180 66 Oct 3, 2026, 10:00 AM 156 72 Oct 3, 2026, 05:00 PM 189 70 Oct 3, 2026, 09:00 PM 141 56 Oct 4, 2026, 07:00 AM 133 66 Oct 4, 2026, 10:00 AM 192 69 Oct 4, 2026, 07:00 PM 177 65 Oct 4, 2026, 10:00 PM 129 60 Oct 5, 2026, 06:00 AM 140 64 Oct 5, 2026, 02:00 PM 246 81 Oct 5, 2026, 09:00 PM 130 61 Oct 6, 2026, 02:00 AM 171 64 Oct 6, 2026, 07:00 AM 143 56 Oct 6, 2026, 03:00 PM 253 88 Oct 6, 2026, 06:00 PM 187 81 Oct 6, 2026, 10:00 PM 168 57 Oct 6, 2026, 11:00 PM 146 57 SLO 300 ms: 300 Showing 120 of 720 points — the ones the chart draws. Arrow keys walk all of them. A logger that went silent
nullbreaks the line instead of dropping it to zero: a silent logger did not measure 0 °C.gaps="dashed"bridges the hole so the trend reads on, and the dash says the bridge is not a measurement. The allowed corridor is a reference band, andcurve="smooth"never overshoots the readings.Fridge 3 · temperatureNo readings 03:00–05:00 — logger offline Chart data X Fridge 3 Oct 6, 2026, 12:00 AM 4.6 Oct 6, 2026, 12:30 AM 4.8 Oct 6, 2026, 01:00 AM 5.1 Oct 6, 2026, 01:30 AM 5.0 Oct 6, 2026, 02:00 AM 4.9 Oct 6, 2026, 02:30 AM 5.3 Oct 6, 2026, 03:00 AM no value Oct 6, 2026, 03:30 AM no value Oct 6, 2026, 04:00 AM no value Oct 6, 2026, 04:30 AM no value Oct 6, 2026, 05:00 AM 7.6 Oct 6, 2026, 05:30 AM 6.9 Oct 6, 2026, 06:00 AM 6.1 Oct 6, 2026, 06:30 AM 5.4 Oct 6, 2026, 07:00 AM 5.0 Oct 6, 2026, 07:30 AM 4.8 Oct 6, 2026, 08:00 AM 4.7 Oct 6, 2026, 08:30 AM 4.9 Oct 6, 2026, 09:00 AM 5.2 Oct 6, 2026, 09:30 AM 5.1 Oct 6, 2026, 10:00 AM 4.8 Oct 6, 2026, 10:30 AM 4.6 Oct 6, 2026, 11:00 AM 4.5 Oct 6, 2026, 11:30 AM 4.7 Oct 6, 2026, 12:00 PM 4.8 2–8 °C: 2.0 to 8.0 Seats that change on a day, not gradually
curve="step"draws a value that jumps and then holds — a sloped line would invent 18.5 seats in March.show-points="always"marks the billed months, andpoint-clickopens the invoice line of the month.Northwind Traders · seats billedTeam plan, €12 a seat · click a month Chart data X Seats Nov 1, 2025, 12:00 AM 12 Dec 1, 2025, 12:00 AM 12 Jan 1, 2026, 12:00 AM 15 Feb 1, 2026, 12:00 AM 15 Mar 1, 2026, 12:00 AM 15 Apr 1, 2026, 12:00 AM 22 May 1, 2026, 12:00 AM 22 Jun 1, 2026, 12:00 AM 22 Jul 1, 2026, 12:00 AM 20 Aug 1, 2026, 12:00 AM 28 Sep 1, 2026, 12:00 AM 28 Oct 1, 2026, 12:00 AM 34 Click a month to see its invoice line.
Two units, two scales
dual-axisgives the conversion its own scale on the right — on the left it would lie flat under 1,700 sign-ups. Each axis has its own tick format, anxreference marks the price change, andv-model:hidden-serieskeeps the legend state with the page: the chart only reports the intent.Growth after the price changeMore people sign up since W32, fewer of them pay Chart data X Sign-ups (left axis) Paid within 14 days (right axis) W28 820 6.1 W29 860 6.0 W30 910 6.3 W31 870 6.4 W32 1,040 5.8 W33 1,180 5.2 W34 1,210 5.0 W35 1,160 5.1 W36 1,390 4.6 W37 1,520 4.3 W38 1,480 4.4 W39 1,610 4.1 W40 1,700 3.9 New prices: W32
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
xScale | GrChartScaleKind | undefined | undefined | Type of the X axis. Not set — inferred from the data. |
height | number | undefined | undefined | The canvas height in pixels; 256 by default. The data, the loading state and the empty state all take up the same height. |
width | number | undefined | 640 | Declared width: the first render starts from it, after that the width is measured. |
yDomain | readonly [number | null, number | null] | undefined | undefined | The bounds of the value axis `[min, max]`; `null` in place of a bound — it is taken from the data. |
includeZero | boolean | undefined | false | Extend the value axis to zero; off by default — the axis fits tightly around the data. |
references | readonly GrChartReference[] | undefined | undefined | Reference lines and bands: a threshold, a plan, a tolerance corridor. Not a series and never treated as one: it stays out of the legend, spends no palette index, joins no stack, and goes into the hidden table as a note rather than a data row. |
includeReferencesInDomain | boolean | undefined | false | Include the references in the axis domain. Off by default, and deliberately so: a threshold of `1.0` with data around `0.03` would stretch the axis until the data itself collapsed into a line. |
dualAxis | boolean | undefined | false | Show a second value axis on the right. Without it, series with `axis: 'right'` land on the left one. Turning it on is a deliberate act: two axes let you fit any two series to a visible correlation, and that has to be the chart author's decision, not a side effect of a field in the data. |
yDomainRight | readonly [number | null, number | null] | undefined | undefined | The bounds of the right value axis with `dualAxis`; `null` in place of a bound — it is taken from the data. |
yTickFormatRight | ((value: number) => string) | undefined | undefined | The tick label of the right axis. Unset — a number formatted by the locale. |
valueFormatRight | GrChartNumberFormat | undefined | undefined | The value format of the right-axis series in the tooltip, the hidden table and announcements. |
xTickCount | number | undefined | undefined | The desired number of ticks on the X axis; 6 by default. For categories — the cap on the number of labels (unset — 12). Labels that do not fit the width are thinned out. |
yTickCount | number | undefined | 5 | The desired number of ticks on the value axis; 5 by default. The step stays "round", so there may be one tick fewer. |
xTickFormat | ChartTickFormat | undefined | undefined | The tick label of the X axis: `(value, kind) => string`. Unset — the category, or a date or number formatted by the locale. |
yTickFormat | ((value: number) => string) | undefined | undefined | The tick label of the value axis, for example `value => value + '%'`. The space for the axis is measured from the formatted label. |
valueFormat | GrChartNumberFormat | undefined | undefined | The value format in the tooltip, the hidden table and announcements: decimal places and separators. |
curve | GrChartCurve | undefined | undefined | The shape of the line between points: `linear` (the default), `smooth` or `step`. Smoothing does not invent values between measurements. |
showPoints | "auto" | "never" | "always" | undefined | "auto" | Point markers: `auto` (the default) — only on a sparse series, `always` or `never`. |
showGrid | "y" | "both" | "x" | "none" | undefined | undefined | Grid lines: `y` (the default) — along the value ticks, `x`, `both` or `none`. |
showLegend | boolean | "auto" | undefined | undefined | The series legend: `auto` (the default) — when there is more than one series; `true` and `false` — always and never. |
legendPosition | "bottom" | "top" | undefined | undefined | Where the legend sits: `bottom` (the default) or `top`. |
tooltip | boolean | undefined | undefined | The panel with the values under the cursor; `true` by default. `false` hides only the panel: the cursor, the active marks, `update:activeIndex` and `pointHover` keep working while the chart is interactive. |
activeIndexv-model | number | null | undefined | undefined | The cursor — `v-model:activeIndex`. Synchronizes a pair of charts. |
loading | boolean | undefined | false | Loading: a skeleton of the same height stands in place of the plot area, and the space for the axes is already reserved. |
empty | boolean | undefined | undefined | The empty state, set explicitly. Unset — the chart is empty when there is nothing to draw. |
emptyText | string | undefined | undefined | The placeholder text of an empty chart. Unset — "No data" from the package translations. |
dataTable | "hidden" | "visible" | "off" | undefined | undefined | The data table: `hidden` (the default) — for the screen reader only, `visible` — under the chart, `off` — no table. |
interactive | boolean | undefined | true | `false` — the chart becomes a picture: no focus, no tooltip, no keyboard. |
size | "md" | "xs" | "sm" | "lg" | undefined | undefined | The type size of the axis and legend labels on the core control scale; by default — the size from `GrConfigProvider`. |
locale | string | undefined | undefined | The BCP-47 locale for numbers and dates. Unset — the language of the application translations, otherwise `en`. |
ariaLabel | string | undefined | undefined | The accessible name of the chart. Unset — a summary the component assembles: the chart type and the amount of data. |
ariaDescription | string | undefined | undefined | A description for the screen reader — the conclusion the chart is shown for; it adds to the summary rather than replacing it. |
decimate | "auto" | "never" | "always" | undefined | undefined | When to thin the series for the drawing (LTTB). `'auto'` kicks in only when there are more points than the budget: above two vertices per pixel SVG would not show what the algorithm dropped anyway, and it pays for that with the length of the `d` string. Thinned points go **only** into the drawing — the cursor, the keyboard, the tooltip and the hidden table know the full series. |
maxPoints | number | undefined | undefined | The point budget per series. Unset — computed from the width of the plot area. |
canvasThreshold | number | undefined | GR_CHART_CANVAS_THRESHOLD | The threshold above which markers are not drawn even with `showPoints: 'always'`. On `'auto'` with the defaults it has no effect: that cuts markers off earlier, at `AUTO_MARKERS_LIMIT`. It starts to matter once the threshold is set below it. |
zoom | GrChartZoom | undefined | undefined | Which gestures the user changes the visible window with. None by default. Driven by the pointer only; the chart has no keyboard path to the window. That is why the window is exposed as `v-model:xWindow` — the application has to give it an accessible path of its own: a reset button, range fields. |
xWindowv-model | readonly [GrChartXValue, GrChartXValue] | null | undefined | undefined | The visible window along the abscissa — `v-model:xWindow`. `null` — the whole series. The window **selects the data**, it does not crop the drawing: positions, the cursor, the keyboard, the hidden table and the value axis span are all computed from it. Decimation is the opposite case: it goes no further than the drawing and leaves the full series alone. Hence the corollary: `activeIndex` addresses the current window. |
dataTableMaxRows | number | "auto" | undefined | undefined | The row cap of the hidden data table. `'auto'` (the default) — as many rows as there are vertices in the drawing: above the cap the table prints the same points that are drawn and says so with a note. A number sets its own cap, `Infinity` lifts it entirely, `dataTable: 'off'` removes the table altogether — which of these is needed is up to the application. |
gaps | "hidden" | "shadow" | "dashed" | undefined | undefined | What to close a break in the series with. `hidden` — nothing: the line breaks, and that is the most honest. `shadow` and `dashed` draw a bridge noticeably different from the line — it shows where the series went during the time without measurements, without pretending to be data itself. |
seriesrequired | readonly number[] | readonly GrChartSeries[] | — | Series or a bare array of numbers — then `x` becomes the ordinal index. |
Slots
| Slot | Type | Description |
|---|---|---|
tooltip | { active: GrChartActivePoint; formatValue: (value: number | null) => string; } | Custom content for the tooltip panel instead of the standard one; the data of the active position comes in the slot props. |
legend | { series: readonly NormalizedSeries[]; toggle: (id: string) => void; } | A custom legend instead of the standard one. It is not drawn for an empty chart. |
empty | any | A custom placeholder for an empty chart instead of "No data"; it takes up the same `height`. |
header | any | A header above the chart, inside its root — above a legend placed on top. |
Events
| Event | Signature | Description |
|---|---|---|
update:hiddenSeries | (event: "update:hiddenSeries", value: string[]): void | — |
update:activeIndex | (event: "update:activeIndex", value: number | null): void | — |
update:xWindow | (event: "update:xWindow", value: GrChartXWindow | null): void | — |
pointClick | (event: "pointClick", value: GrChartActivePoint): void | — |
pointHover | (event: "pointHover", value: GrChartActivePoint | null): void | — |
legendToggle | (event: "legendToggle", value: { seriesId: string; hidden: boolean; }): void | — |
Methods
| Methods | Signature | Description |
|---|---|---|
element | HTMLElement | null | The root element — for measuring and scrolling in the consumer. |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrChartLineConfigurableProps, GrChartSize, GrChartLineProps } from '@feugene/granularity-charts/components/GrChartLine'Filedist/types/components/GrChartLine/config.d.ts
const grChartLineConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrChartLine">;Filedist/types/components/GrChartLine/defaults.d.ts
interface GrChartLineConfigurableProps {
decimate: 'auto' | 'always' | 'never';
maxPoints: number;
zoom: GrChartZoom;
dataTableMaxRows: number | 'auto';
size: GrChartSize;
height: number;
curve: GrChartCurve;
gaps: 'hidden' | 'shadow' | 'dashed';
showGrid: 'both' | 'x' | 'y' | 'none';
showLegend: boolean | 'auto';
legendPosition: 'top' | 'bottom';
tooltip: boolean;
dataTable: 'hidden' | 'visible' | 'off';
}Filedist/types/components/GrChartFrame/chartFrameStyles.d.ts
type GrChartSize = GrComponentSize;Filedist/types/components/GrChartLine/safelist.d.ts
const grChartLineSafelist: string[];Filedist/types/components/GrChartLine/GrChartLine.vue.d.ts
interface GrChartLineProps {
series: readonly GrChartSeries[] | readonly number[];
xScale?: GrChartScaleKind;
height?: number;
width?: number;
yDomain?: readonly [number | null, number | null];
includeZero?: boolean;
references?: readonly GrChartReference[];
includeReferencesInDomain?: boolean;
dualAxis?: boolean;
yDomainRight?: readonly [number | null, number | null];
yTickFormatRight?: (value: number) => string;interface GrChartLineEmits {
(e: 'update:hiddenSeries', value: string[]): void;
(e: 'update:activeIndex', value: number | null): void;
(e: 'update:xWindow', value: GrChartXWindow | null): void;
(e: 'pointClick', value: GrChartActivePoint): void;
(e: 'pointHover', value: GrChartActivePoint | null): void;
(e: 'legendToggle', value: {
seriesId: string;
hidden: boolean;
}): void;
}Accessibility
APG pattern
При нескольких сериях ↑/↓ переключают читаемую серию
Full contract of the package: docs/keyboard.md ↗
Usage from the library prose
Smoothing does not invent values
curve: 'smooth' is a monotone cubic: it does not throw the curve beyond the range of the
neighbouring values. An ordinary cubic spline on a sharp drop gives a “hump” below the minimum or
above the maximum of a pair — that is, it draws a value that is not in the data. step is
appropriate where a quantity really did change in steps: a tariff, a version, a status.
The gaps bridge is always straight, even with smooth. A curve across a gap would have a shape,
that is, it would show the course of a value where it was not measured.
The marks appear by themselves
showPoints: 'auto' draws the marks while the series is short (up to sixty points). On a long series
they merge into a solid band and get in the way of reading the line, so they disappear. 'always' on
fifty thousand points is a deliberate choice of the consumer, and it costs exactly what it costs.
Above the threshold it is drawn by a canvas
The body of the chart can be drawn in two ways. Below the threshold it is SVG, above it a
<canvas>; the component chooses itself, and there is no “which renderer” prop.
The threshold is counted in drawn vertices rather than in points — canvasThreshold, 24,000 by
default. The difference is substantial: decimation cuts every series down to the limit of the
screen (about two vertices per pixel), so one series of a hundred thousand points is drawn as 2,400
vertices and costs milliseconds, while twenty series of 2,400 — the same 48,000 points — cost
sixteen, that is, a whole frame.
The measured numbers at a width of 1200px, 2,400 vertices per series:
| Series | SVG | Canvas |
|---|---|---|
| 1 | 1.1 ms | 0.4 ms |
| 8 | 6.7 ms | 0.6 ms |
| 20 | 16.3 ms | 1.7 ms |
canvasThreshold: 0 switches the canvas off entirely — for the case where the drawing has to stay
vector: printing, an SVG export, CSS of your own over the marks.
Accessibility does not change at all with the change of renderer. The cursor, the keyboard, the
tooltip and the hidden table work with the overlay and with the full series rather than with the
marks: the canvas does not exist for them, it is aria-hidden and does not catch the pointer. That
was the condition on which a second renderer was allowed at all.
What the canvas draws differently. The grid moves into it as well: the canvas lies under the
<svg> so that the axes and the active point stay on top — and the grid has to stay under the
series. In an area chart the gradient fill becomes solid: the canvas does not understand url(#…),
and across twenty areas a gradient reads as a mess anyway.
The cursor and the keyboard work with one state
activeIndex is a v-model: a pair of charts is synchronised with it, so that the cursor in one
highlights the same abscissa in the other. hiddenSeries is a v-model too, but the component does
not apply it itself: the legend emits the intent, and the state belongs to the consumer.
tooltip: false hides only the panel. The cursor under the pointer and from the keyboard, the
active marks, update:activeIndex and pointHover stay: on the second chart of a pair the panel is
often redundant, but the cursor is needed — it shows the same abscissa.
interactive: false turns the chart into a picture: a role="img" with a name, with no focus, no
tooltip and no keyboard. That is the mode for printing and for a tile that is clicked as a whole.
Zooming along the abscissa
zoom switches the window on: 'brush' is a drag across the canvas, 'wheel' is the wheel, and
'both' is both. It is off by default.
<GrChartLine v-model:x-window="window" :series="series" zoom="both" />
<GrButton :disabled="window === null" @click="window = null">
The whole series
</GrButton>The window selects the data rather than cropping the drawing: the positions, the cursor, the keyboard, the hidden table and the span of the value axis are computed from it. The practical consequence is that zooming reveals the fine structure that on the full series lies as a solid hatching: the decimation budget is computed from the width of the area, and there are fewer points in the window, so more vertices fall to each of them.
The keyboard works whenever zoom is on: +/- zoom towards the active point, Shift+arrows
shift the window, and 0 returns the whole series. The union of the prop enumerates only pointer
gestures — zooming has no switchable keyboard by design (../a11y.md, the section
on zooming).
v-model:x-window is not mandatory — without a binding the chart zooms by itself. It is bound for
something else: a synchronised pair of charts, a reset button next to the canvas, keeping the zoom in
the address bar.
The bounds are accepted in the same form as the abscissas of the points (Date, an ISO string, a
number) and go out as numbers. With a window set, activeIndex addresses it rather than the
whole series.
A threshold is drawn as a reference, not as a series
A plan, a norm and the limit of the acceptable are the references prop rather than a series made of a constant: a
constant series would enter the legend, would stretch the axis and would travel into the table as data. The details —
../model.md, the section “A reference is not a series”.
Two axes are a deliberate decision
Series of different orders (money and units) are readable on one chart only with two axes, and two
axes let any pair be fitted to a visible correlation. That is why the axis: 'right' of a series
does not work until dualAxis is switched on. The invariants —
../model.md, the section “The second value axis”.
Props in this example