@cfasim-ui/charts 0.7.8 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BarChart/BarChart.d.ts +9 -26
- package/dist/ChartMenu/ChartMenu.d.ts +3 -2
- package/dist/ChartTooltip/ChartTooltip.d.ts +9 -12
- package/dist/ChoroplethMap/ChoroplethMap.d.ts +42 -190
- package/dist/ChoroplethMap/ChoroplethTooltip.d.ts +20 -27
- package/dist/ChoroplethMap/canvasLayer.d.ts +23 -6
- package/dist/ChoroplethMap/cityLayout.d.ts +3 -2
- package/dist/ChoroplethMap/mixedGeo.d.ts +74 -0
- package/dist/ChoroplethMap/mixedGeo.test.d.ts +1 -0
- package/dist/DataTable/DataTable.d.ts +3 -2
- package/dist/LineChart/LineChart.d.ts +9 -26
- package/dist/_shared/ChartAnnotations.d.ts +3 -2
- package/dist/_shared/ChartZoomControls.d.ts +3 -2
- package/dist/_shared/index.d.ts +2 -1
- package/dist/_shared/mapTheme.d.ts +159 -0
- package/dist/_shared/mapTheme.test.d.ts +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1821 -1462
- package/docs/BarChart.md +776 -0
- package/docs/ChoroplethMap.md +1394 -0
- package/docs/DataTable.md +386 -0
- package/docs/LineChart.md +1267 -0
- package/docs/index.json +56 -0
- package/package.json +25 -21
- package/src/BarChart/BarChart.md +743 -0
- package/src/BarChart/BarChart.vue +1901 -0
- package/src/ChartMenu/ChartMenu.vue +220 -0
- package/src/ChartMenu/download.ts +120 -0
- package/src/ChartTooltip/ChartTooltip.vue +97 -0
- package/src/ChoroplethMap/ChoroplethMap.md +1354 -0
- package/src/ChoroplethMap/ChoroplethMap.vue +3778 -0
- package/src/ChoroplethMap/ChoroplethTooltip.vue +103 -0
- package/src/ChoroplethMap/canvasLayer.ts +373 -0
- package/src/ChoroplethMap/cityLayout.ts +262 -0
- package/src/ChoroplethMap/hsaMapping.ts +4116 -0
- package/src/ChoroplethMap/mixedGeo.ts +201 -0
- package/src/DataTable/DataTable.md +372 -0
- package/src/DataTable/DataTable.vue +406 -0
- package/src/LineChart/LineChart.md +1225 -0
- package/src/LineChart/LineChart.vue +1555 -0
- package/src/_shared/ChartAnnotations.vue +420 -0
- package/src/_shared/ChartZoomControls.vue +138 -0
- package/src/_shared/annotations.ts +106 -0
- package/src/_shared/axes.ts +69 -0
- package/src/_shared/chartProps.ts +201 -0
- package/src/_shared/computeTicks.ts +42 -0
- package/src/_shared/contrast.ts +100 -0
- package/src/_shared/dateAxis.ts +501 -0
- package/src/_shared/index.ts +78 -0
- package/src/_shared/mapTheme.ts +551 -0
- package/src/_shared/scale.ts +86 -0
- package/src/_shared/seriesCsv.ts +68 -0
- package/src/_shared/touch.ts +8 -0
- package/src/_shared/useChartFoundation.ts +175 -0
- package/src/_shared/useChartFullscreen.ts +254 -0
- package/src/_shared/useChartMenu.ts +111 -0
- package/src/_shared/useChartPadding.ts +235 -0
- package/src/_shared/useChartSize.ts +58 -0
- package/src/_shared/useChartTooltip.ts +205 -0
- package/src/env.d.ts +4 -0
- package/src/hsa-mapping.ts +1 -0
- package/src/index.ts +41 -0
- package/src/tooltip-position.ts +55 -0
- package/src/us-cities/data.ts +1371 -0
- package/src/us-cities/index.ts +122 -0
- package/src/us-cities.ts +7 -0
|
@@ -0,0 +1,1225 @@
|
|
|
1
|
+
---
|
|
2
|
+
keywords: [line, chart, time-series, series, axis, area, confidence band, svg]
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# LineChart
|
|
6
|
+
|
|
7
|
+
A responsive SVG line chart with support for multiple series, axis labels, and custom styling.
|
|
8
|
+
|
|
9
|
+
## Examples
|
|
10
|
+
|
|
11
|
+
### Single series
|
|
12
|
+
|
|
13
|
+
<ComponentDemo>
|
|
14
|
+
<LineChart :data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]" :height="200" x-label="Days" y-label="Cases" tooltip-trigger="hover" />
|
|
15
|
+
|
|
16
|
+
<template #code>
|
|
17
|
+
|
|
18
|
+
```vue
|
|
19
|
+
<LineChart
|
|
20
|
+
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
|
|
21
|
+
:height="200"
|
|
22
|
+
x-label="Days"
|
|
23
|
+
y-label="Cases"
|
|
24
|
+
tooltip-trigger="hover"
|
|
25
|
+
/>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
</template>
|
|
29
|
+
</ComponentDemo>
|
|
30
|
+
|
|
31
|
+
### x/y
|
|
32
|
+
|
|
33
|
+
Pass paired `x` and `y` arrays to plot points at specific x positions.
|
|
34
|
+
`y` is equivalent to `data` (both names are accepted), and they both
|
|
35
|
+
take typed arrays. For multi-series
|
|
36
|
+
charts, set `x` and `y` (or `data`) on each `Series`.
|
|
37
|
+
|
|
38
|
+
<ComponentDemo>
|
|
39
|
+
<LineChart
|
|
40
|
+
:x="[0, 1, 2, 5, 10, 20, 50]"
|
|
41
|
+
:y="[0, 2, 5, 12, 22, 30, 38]"
|
|
42
|
+
:height="200"
|
|
43
|
+
x-label="Days (log-ish)"
|
|
44
|
+
y-label="Cases"
|
|
45
|
+
tooltip-trigger="hover"
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
<template #code>
|
|
49
|
+
|
|
50
|
+
```vue
|
|
51
|
+
<LineChart
|
|
52
|
+
:x="[0, 1, 2, 5, 10, 20, 50]"
|
|
53
|
+
:y="[0, 2, 5, 12, 22, 30, 38]"
|
|
54
|
+
:height="200"
|
|
55
|
+
x-label="Days"
|
|
56
|
+
y-label="Cases"
|
|
57
|
+
tooltip-trigger="hover"
|
|
58
|
+
/>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
</template>
|
|
62
|
+
</ComponentDemo>
|
|
63
|
+
|
|
64
|
+
When `x` is omitted, `y`/`data` values are plotted at indices 0, 1, 2, etc.
|
|
65
|
+
|
|
66
|
+
### Dates
|
|
67
|
+
|
|
68
|
+
Pass ISO date strings (`YYYY-MM-DD`) or `Date` objects as `x` and the
|
|
69
|
+
chart auto-detects a date axis: ticks anchor to year / month / week /
|
|
70
|
+
day / hour boundaries depending on the visible range, and labels
|
|
71
|
+
format themselves accordingly. Numeric `x` arrays keep their existing
|
|
72
|
+
behavior — auto-detection is content-driven and never promotes plain
|
|
73
|
+
numbers.
|
|
74
|
+
|
|
75
|
+
<ComponentDemo>
|
|
76
|
+
<LineChart
|
|
77
|
+
:x="['2026-01-05', '2026-01-12', '2026-01-19', '2026-01-26', '2026-02-02', '2026-02-09', '2026-02-16', '2026-02-23', '2026-03-02', '2026-03-09']"
|
|
78
|
+
:y="[4, 9, 18, 32, 41, 38, 27, 18, 11, 6]"
|
|
79
|
+
:height="220"
|
|
80
|
+
y-label="% ED visits"
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
<template #code>
|
|
84
|
+
|
|
85
|
+
```vue
|
|
86
|
+
<LineChart
|
|
87
|
+
:x="[
|
|
88
|
+
'2026-01-05',
|
|
89
|
+
'2026-01-12',
|
|
90
|
+
'2026-01-19',
|
|
91
|
+
'2026-01-26',
|
|
92
|
+
'2026-02-02',
|
|
93
|
+
'2026-02-09',
|
|
94
|
+
'2026-02-16',
|
|
95
|
+
'2026-02-23',
|
|
96
|
+
'2026-03-02',
|
|
97
|
+
'2026-03-09',
|
|
98
|
+
]"
|
|
99
|
+
:y="[4, 9, 18, 32, 41, 38, 27, 18, 11, 6]"
|
|
100
|
+
:height="220"
|
|
101
|
+
y-label="% ED visits"
|
|
102
|
+
/>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
</template>
|
|
106
|
+
</ComponentDemo>
|
|
107
|
+
|
|
108
|
+
Override the format via `x-tick-format`. Presets: `"iso"`,
|
|
109
|
+
`"iso-datetime"`, `"year"`, `"month-year"`, `"month-day"`, `"day"`,
|
|
110
|
+
`"time"`, `"datetime"`, `"medium"`. You can also pass an
|
|
111
|
+
`Intl.DateTimeFormatOptions` literal, or a function `(ms, unit?) =>
|
|
112
|
+
string` for full control.
|
|
113
|
+
|
|
114
|
+
<ComponentDemo>
|
|
115
|
+
<LineChart
|
|
116
|
+
:x="['2026-01-05', '2026-01-12', '2026-01-19', '2026-01-26', '2026-02-02']"
|
|
117
|
+
:y="[4, 9, 18, 32, 41]"
|
|
118
|
+
x-tick-format="iso"
|
|
119
|
+
:height="220"
|
|
120
|
+
y-label="% ED visits"
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
<template #code>
|
|
124
|
+
|
|
125
|
+
```vue
|
|
126
|
+
<LineChart
|
|
127
|
+
:x="['2026-01-05', '2026-01-12', '2026-01-19', '2026-01-26', '2026-02-02']"
|
|
128
|
+
:y="[4, 9, 18, 32, 41]"
|
|
129
|
+
x-tick-format="iso"
|
|
130
|
+
:height="220"
|
|
131
|
+
y-label="% ED visits"
|
|
132
|
+
/>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
</template>
|
|
136
|
+
</ComponentDemo>
|
|
137
|
+
|
|
138
|
+
By default the chart parses bare `YYYY-MM-DD` (and offset-less
|
|
139
|
+
`YYYY-MM-DDTHH:mm:ss`) as UTC and renders tick labels in UTC. Set
|
|
140
|
+
`timezone="local"` to interpret offset-less strings in the browser's
|
|
141
|
+
timezone instead. Strings that include an explicit offset (`Z` or
|
|
142
|
+
`+05:00`) are always parsed exactly as written.
|
|
143
|
+
|
|
144
|
+
> **Annotations on a date axis.** `ChartAnnotation.x` is always in the
|
|
145
|
+
> chart's resolved coordinate space — that's **epoch-ms** when the axis
|
|
146
|
+
> is in date mode. Pass `Date.UTC(2026, 0, 15)`, `new Date("2026-01-15").getTime()`,
|
|
147
|
+
> or `Date.parse("2026-01-15T00:00:00Z")`. Passing a small integer like
|
|
148
|
+
> `0` will place the annotation at 1970-01-01 (off-screen).
|
|
149
|
+
>
|
|
150
|
+
> **`xLabels` on a date axis.** When both `x: [...date strings]` and
|
|
151
|
+
> `xLabels` are supplied, the date axis wins — `xLabels` is ignored.
|
|
152
|
+
> Date strings carry their own labeling.
|
|
153
|
+
|
|
154
|
+
### Title
|
|
155
|
+
|
|
156
|
+
The `title` prop accepts `\n` to break across lines. Use `titleStyle`
|
|
157
|
+
to override `fontSize`, `lineHeight`, `color`, `fontWeight`, and
|
|
158
|
+
`align` (`"left" | "center" | "right"`, default `"left"`). Top padding
|
|
159
|
+
grows automatically for additional lines.
|
|
160
|
+
|
|
161
|
+
<ComponentDemo>
|
|
162
|
+
<LineChart
|
|
163
|
+
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
|
|
164
|
+
:height="220"
|
|
165
|
+
:title="'Daily ED visits\nInfluenza-like illness, last 11 days'"
|
|
166
|
+
:title-style="{ fontSize: 16, lineHeight: 20, color: '#0057b7' }"
|
|
167
|
+
x-label="Days"
|
|
168
|
+
y-label="Cases"
|
|
169
|
+
/>
|
|
170
|
+
|
|
171
|
+
<template #code>
|
|
172
|
+
|
|
173
|
+
```vue
|
|
174
|
+
<LineChart
|
|
175
|
+
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
|
|
176
|
+
:height="220"
|
|
177
|
+
:title="'Daily ED visits\nInfluenza-like illness, last 11 days'"
|
|
178
|
+
:title-style="{ fontSize: 16, lineHeight: 20, color: '#0057b7' }"
|
|
179
|
+
x-label="Days"
|
|
180
|
+
y-label="Cases"
|
|
181
|
+
/>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
</template>
|
|
185
|
+
</ComponentDemo>
|
|
186
|
+
|
|
187
|
+
### Font sizes
|
|
188
|
+
|
|
189
|
+
Every text element on the chart has its own style prop and default size (in px). The text-styling props (`axisLabelStyle`, `tickLabelStyle`, `legendStyle`) take a `LabelStyle` — `{ fontSize?, color?, fontWeight? }` — and `titleStyle` adds `lineHeight` and `align`. Unset fields fall back to the defaults below.
|
|
190
|
+
|
|
191
|
+
| Chart part | Prop / field | Type | Default size |
|
|
192
|
+
| ------------------------------------------- | ------------------------------------ | ------------ | -----------: |
|
|
193
|
+
| Title | `titleStyle` | `TitleStyle` | 14 |
|
|
194
|
+
| Axis labels (`xLabel` / `yLabel`) | `axisLabelStyle` | `LabelStyle` | 13 |
|
|
195
|
+
| Axis tick labels (the numbers on each axis) | `tickLabelStyle` | `LabelStyle` | 10 |
|
|
196
|
+
| Inline legend | `legendStyle` | `LabelStyle` | 11 |
|
|
197
|
+
| Area section label | `areaSection.inlineLabelStyle` | `LabelStyle` | 11 |
|
|
198
|
+
| Area section description | `areaSection.inlineDescriptionStyle` | `LabelStyle` | 11 |
|
|
199
|
+
| Annotation text | `annotation.fontSize` | `number` | 13 |
|
|
200
|
+
|
|
201
|
+
These are single styles applied to both axes — there's no separate x vs y font size. The same props (except the LineChart-only area-section fields) exist on `BarChart`, since both compose `ChartCommonProps`.
|
|
202
|
+
|
|
203
|
+
<ComponentDemo>
|
|
204
|
+
<LineChart
|
|
205
|
+
:series="[
|
|
206
|
+
{ data: [0, 12, 28, 45, 60, 55, 40, 25, 12], color: '#0057b7', legend: 'No interventions' },
|
|
207
|
+
{ data: [0, 8, 18, 30, 40, 35, 25, 14, 6], color: '#ef4444', legend: 'Masking + distancing' },
|
|
208
|
+
]"
|
|
209
|
+
:height="240"
|
|
210
|
+
title="Daily ED visits"
|
|
211
|
+
x-label="Days"
|
|
212
|
+
y-label="Cases"
|
|
213
|
+
:title-style="{ fontSize: 18 }"
|
|
214
|
+
:axis-label-style="{ fontSize: 15, fontWeight: 700 }"
|
|
215
|
+
:tick-label-style="{ fontSize: 13 }"
|
|
216
|
+
:legend-style="{ fontSize: 14 }"
|
|
217
|
+
/>
|
|
218
|
+
|
|
219
|
+
<template #code>
|
|
220
|
+
|
|
221
|
+
```vue
|
|
222
|
+
<LineChart
|
|
223
|
+
:series="[
|
|
224
|
+
{
|
|
225
|
+
data: [0, 12, 28, 45, 60, 55, 40, 25, 12],
|
|
226
|
+
color: '#0057b7',
|
|
227
|
+
legend: 'No interventions',
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
data: [0, 8, 18, 30, 40, 35, 25, 14, 6],
|
|
231
|
+
color: '#ef4444',
|
|
232
|
+
legend: 'Masking + distancing',
|
|
233
|
+
},
|
|
234
|
+
]"
|
|
235
|
+
:height="240"
|
|
236
|
+
title="Daily ED visits"
|
|
237
|
+
x-label="Days"
|
|
238
|
+
y-label="Cases"
|
|
239
|
+
:title-style="{ fontSize: 18 }"
|
|
240
|
+
:axis-label-style="{ fontSize: 15, fontWeight: 700 }"
|
|
241
|
+
:tick-label-style="{ fontSize: 13 }"
|
|
242
|
+
:legend-style="{ fontSize: 14 }"
|
|
243
|
+
/>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
</template>
|
|
247
|
+
</ComponentDemo>
|
|
248
|
+
|
|
249
|
+
### Multiple series
|
|
250
|
+
|
|
251
|
+
<ComponentDemo>
|
|
252
|
+
<LineChart
|
|
253
|
+
:series="[
|
|
254
|
+
{ data: [0, 10, 25, 45, 60, 55, 40, 20, 8], color: '#fb7e38', strokeWidth: 3 },
|
|
255
|
+
{ x: [0, 1, 3, 4, 6, 7, 8], y: [0, 5, 20, 28, 18, 10, 4], color: '#0057b7', strokeWidth: 3 },
|
|
256
|
+
]"
|
|
257
|
+
:height="200"
|
|
258
|
+
x-label="Weeks"
|
|
259
|
+
y-label="Incidence"
|
|
260
|
+
/>
|
|
261
|
+
|
|
262
|
+
<template #code>
|
|
263
|
+
|
|
264
|
+
```vue
|
|
265
|
+
<LineChart
|
|
266
|
+
:series="[
|
|
267
|
+
{
|
|
268
|
+
data: [0, 10, 25, 45, 60, 55, 40, 20, 8],
|
|
269
|
+
color: '#fb7e38',
|
|
270
|
+
strokeWidth: 3,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
x: [0, 1, 3, 4, 6, 7, 8],
|
|
274
|
+
y: [0, 5, 20, 28, 18, 10, 4],
|
|
275
|
+
color: '#0057b7',
|
|
276
|
+
strokeWidth: 3,
|
|
277
|
+
},
|
|
278
|
+
]"
|
|
279
|
+
:height="200"
|
|
280
|
+
x-label="Weeks"
|
|
281
|
+
y-label="Incidence"
|
|
282
|
+
/>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
</template>
|
|
286
|
+
</ComponentDemo>
|
|
287
|
+
|
|
288
|
+
### Legend
|
|
289
|
+
|
|
290
|
+
Set `legend` on a series to add an entry to the inline legend strip above the plot. When the items don't all fit on one row at the chart's current width, the legend wraps to additional rows and the plot area shrinks to keep the legend clear of the data. Use `showInLegend: false` to keep a series off the legend (e.g. when its `legend` string is reused as a CSV column header) and `showInTooltip: false` to omit it from the hover tooltip.
|
|
291
|
+
|
|
292
|
+
<ComponentDemo>
|
|
293
|
+
<LineChart
|
|
294
|
+
:series="[
|
|
295
|
+
{ data: [0, 12, 28, 45, 60, 55, 40, 25, 12], color: '#0057b7', legend: 'No interventions' },
|
|
296
|
+
{ data: [0, 10, 22, 38, 50, 45, 32, 18, 8], color: '#fb7e38', legend: 'School closures' },
|
|
297
|
+
{ data: [0, 8, 18, 30, 40, 35, 25, 14, 6], color: '#ef4444', legend: 'Masking + distancing' },
|
|
298
|
+
{ data: [0, 6, 14, 24, 32, 28, 20, 10, 4], color: '#10b981', legend: 'Vaccination only' },
|
|
299
|
+
{ data: [0, 4, 10, 16, 22, 19, 13, 7, 3], color: '#6366f1', legend: 'Vaccination + masking' },
|
|
300
|
+
{ data: [0, 3, 7, 11, 15, 13, 9, 5, 2], color: '#a855f7', legend: 'All interventions combined' },
|
|
301
|
+
]"
|
|
302
|
+
:height="240"
|
|
303
|
+
x-label="Weeks"
|
|
304
|
+
y-label="Incidence"
|
|
305
|
+
/>
|
|
306
|
+
|
|
307
|
+
<template #code>
|
|
308
|
+
|
|
309
|
+
```vue
|
|
310
|
+
<LineChart
|
|
311
|
+
:series="[
|
|
312
|
+
{ data: scenarioA, color: '#0057b7', legend: 'No interventions' },
|
|
313
|
+
{ data: scenarioB, color: '#fb7e38', legend: 'School closures' },
|
|
314
|
+
{ data: scenarioC, color: '#ef4444', legend: 'Masking + distancing' },
|
|
315
|
+
{ data: scenarioD, color: '#10b981', legend: 'Vaccination only' },
|
|
316
|
+
{ data: scenarioE, color: '#6366f1', legend: 'Vaccination + masking' },
|
|
317
|
+
{ data: scenarioF, color: '#a855f7', legend: 'All interventions combined' },
|
|
318
|
+
]"
|
|
319
|
+
:height="240"
|
|
320
|
+
x-label="Weeks"
|
|
321
|
+
y-label="Incidence"
|
|
322
|
+
/>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
</template>
|
|
326
|
+
</ComponentDemo>
|
|
327
|
+
|
|
328
|
+
### Tooltip
|
|
329
|
+
|
|
330
|
+
Hover over the chart to see a tooltip with values at each data point. Set `tooltip-trigger="hover"` to enable the built-in tooltip with crosshair and highlight dots. Use the `#tooltip` slot for custom content. Pass `tooltip-value-format` to control how numeric values render (e.g. percentages, currency); it falls back to `y-tick-format` when omitted.
|
|
331
|
+
|
|
332
|
+
On touch devices, drag **horizontally** across the chart to scrub the tooltip between points; a **vertical** swipe scrolls the page instead of getting captured by the chart.
|
|
333
|
+
|
|
334
|
+
<ComponentDemo>
|
|
335
|
+
<LineChart
|
|
336
|
+
:series="[
|
|
337
|
+
{ data: [0, 10, 25, 45, 60, 55, 40, 20, 8], color: '#fb7e38', strokeWidth: 3 },
|
|
338
|
+
{ data: [0, 5, 12, 20, 28, 25, 18, 10, 4], color: '#0057b7', strokeWidth: 3 },
|
|
339
|
+
]"
|
|
340
|
+
:x-tick-format="(_, i) => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep'][i]"
|
|
341
|
+
:height="200"
|
|
342
|
+
x-label="Month"
|
|
343
|
+
y-label="Incidence"
|
|
344
|
+
tooltip-trigger="hover"
|
|
345
|
+
/>
|
|
346
|
+
|
|
347
|
+
<template #code>
|
|
348
|
+
|
|
349
|
+
```vue
|
|
350
|
+
<LineChart
|
|
351
|
+
:series="[
|
|
352
|
+
{
|
|
353
|
+
data: [0, 10, 25, 45, 60, 55, 40, 20, 8],
|
|
354
|
+
color: '#fb7e38',
|
|
355
|
+
strokeWidth: 3,
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
data: [0, 5, 12, 20, 28, 25, 18, 10, 4],
|
|
359
|
+
color: '#0057b7',
|
|
360
|
+
strokeWidth: 3,
|
|
361
|
+
},
|
|
362
|
+
]"
|
|
363
|
+
:x-tick-format="(_, i) => months[i]"
|
|
364
|
+
:height="200"
|
|
365
|
+
x-label="Month"
|
|
366
|
+
y-label="Incidence"
|
|
367
|
+
tooltip-trigger="hover"
|
|
368
|
+
/>
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
</template>
|
|
372
|
+
</ComponentDemo>
|
|
373
|
+
|
|
374
|
+
### Custom axis ticks
|
|
375
|
+
|
|
376
|
+
Control tick placement with `x-ticks` and `y-ticks`. Pass a **number** for a fixed interval (in data units, respecting `xMin`) or an **array** of explicit values. Use `x-tick-format` / `y-tick-format` to customize labels.
|
|
377
|
+
|
|
378
|
+
<ComponentDemo>
|
|
379
|
+
<LineChart
|
|
380
|
+
:data="[0, 0.12, 0.28, 0.45, 0.61, 0.74, 0.83, 0.89, 0.93, 0.96, 0.97, 0.98, 0.99, 0.99, 1.0]"
|
|
381
|
+
:x-ticks="7"
|
|
382
|
+
:y-ticks="[0, 0.5, 1]"
|
|
383
|
+
:y-tick-format="(v) => `${(v * 100).toFixed(0)}%`"
|
|
384
|
+
:x-tick-format="(v) => `day ${v}`"
|
|
385
|
+
:height="220"
|
|
386
|
+
x-label="Time"
|
|
387
|
+
y-label="Coverage"
|
|
388
|
+
x-grid
|
|
389
|
+
y-grid
|
|
390
|
+
/>
|
|
391
|
+
|
|
392
|
+
<template #code>
|
|
393
|
+
|
|
394
|
+
```vue
|
|
395
|
+
<LineChart
|
|
396
|
+
:data="coverage"
|
|
397
|
+
:x-ticks="7"
|
|
398
|
+
:y-ticks="[0, 0.5, 1]"
|
|
399
|
+
:y-tick-format="(v) => `${(v * 100).toFixed(0)}%`"
|
|
400
|
+
:x-tick-format="(v) => `day ${v}`"
|
|
401
|
+
:height="220"
|
|
402
|
+
x-label="Time"
|
|
403
|
+
y-label="Coverage"
|
|
404
|
+
x-grid
|
|
405
|
+
y-grid
|
|
406
|
+
/>
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
</template>
|
|
410
|
+
</ComponentDemo>
|
|
411
|
+
|
|
412
|
+
### Logarithmic y-axis
|
|
413
|
+
|
|
414
|
+
Set `y-scale-type="log"` to switch the y axis to base-10 log scaling. Useful
|
|
415
|
+
when data spans several orders of magnitude (e.g. epidemic case counts in
|
|
416
|
+
early exponential growth). Non-positive values collapse to the axis
|
|
417
|
+
floor, and `yMin <= 0` is ignored.
|
|
418
|
+
|
|
419
|
+
<ComponentDemo>
|
|
420
|
+
<LineChart
|
|
421
|
+
:data="[1, 3, 8, 22, 60, 165, 450, 1230, 3350]"
|
|
422
|
+
y-scale-type="log"
|
|
423
|
+
:height="220"
|
|
424
|
+
x-label="Day"
|
|
425
|
+
y-label="Cases"
|
|
426
|
+
y-grid
|
|
427
|
+
/>
|
|
428
|
+
|
|
429
|
+
<template #code>
|
|
430
|
+
|
|
431
|
+
```vue
|
|
432
|
+
<LineChart
|
|
433
|
+
:data="[1, 3, 8, 22, 60, 165, 450, 1230, 3350]"
|
|
434
|
+
y-scale-type="log"
|
|
435
|
+
:height="220"
|
|
436
|
+
x-label="Day"
|
|
437
|
+
y-label="Cases"
|
|
438
|
+
y-grid
|
|
439
|
+
/>
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
</template>
|
|
443
|
+
</ComponentDemo>
|
|
444
|
+
|
|
445
|
+
### Dashed baseline
|
|
446
|
+
|
|
447
|
+
<ComponentDemo>
|
|
448
|
+
<LineChart
|
|
449
|
+
:series="[
|
|
450
|
+
{ data: [0, 10, 25, 45, 60, 55, 40, 20, 8], color: '#999', dashed: true, strokeWidth: 2 },
|
|
451
|
+
{ data: [0, 5, 12, 20, 28, 25, 18, 10, 4], color: '#2563eb', strokeWidth: 2 },
|
|
452
|
+
]"
|
|
453
|
+
:height="200"
|
|
454
|
+
/>
|
|
455
|
+
|
|
456
|
+
<template #code>
|
|
457
|
+
|
|
458
|
+
```vue
|
|
459
|
+
<LineChart
|
|
460
|
+
:series="[
|
|
461
|
+
{
|
|
462
|
+
data: [0, 10, 25, 45, 60, 55, 40, 20, 8],
|
|
463
|
+
color: '#999',
|
|
464
|
+
dashed: true,
|
|
465
|
+
strokeWidth: 2,
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
data: [0, 5, 12, 20, 28, 25, 18, 10, 4],
|
|
469
|
+
color: '#2563eb',
|
|
470
|
+
strokeWidth: 2,
|
|
471
|
+
},
|
|
472
|
+
]"
|
|
473
|
+
:height="200"
|
|
474
|
+
/>
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
</template>
|
|
478
|
+
</ComponentDemo>
|
|
479
|
+
|
|
480
|
+
### Outline
|
|
481
|
+
|
|
482
|
+
Set `outline: true` on a series to draw a page-colored stroke behind the
|
|
483
|
+
line. This is useful when lines overlap, cross dense areas, or sit on a
|
|
484
|
+
busy background — the outline keeps each series visually distinct.
|
|
485
|
+
|
|
486
|
+
Override the outline's color with `outlineColor` (any CSS color; defaults
|
|
487
|
+
to the page background `var(--color-bg-0, #fff)`) and its thickness with
|
|
488
|
+
`outlineWidth` (extra width added to the line's `strokeWidth`, split evenly
|
|
489
|
+
per side; defaults to `4`).
|
|
490
|
+
|
|
491
|
+
<ComponentDemo>
|
|
492
|
+
<LineChart
|
|
493
|
+
:series="[
|
|
494
|
+
{ data: [0, 10, 25, 45, 60, 55, 40, 20, 8], color: '#fb7e38', strokeWidth: 3, outline: true },
|
|
495
|
+
{ data: [0, 12, 22, 40, 58, 58, 42, 22, 10], color: '#0057b7', strokeWidth: 3, outline: true },
|
|
496
|
+
]"
|
|
497
|
+
:height="200"
|
|
498
|
+
/>
|
|
499
|
+
|
|
500
|
+
<template #code>
|
|
501
|
+
|
|
502
|
+
```vue
|
|
503
|
+
<LineChart
|
|
504
|
+
:series="[
|
|
505
|
+
{
|
|
506
|
+
data: [0, 10, 25, 45, 60, 55, 40, 20, 8],
|
|
507
|
+
color: '#fb7e38',
|
|
508
|
+
strokeWidth: 3,
|
|
509
|
+
outline: true,
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
data: [0, 12, 22, 40, 58, 58, 42, 22, 10],
|
|
513
|
+
color: '#0057b7',
|
|
514
|
+
strokeWidth: 3,
|
|
515
|
+
outline: true,
|
|
516
|
+
},
|
|
517
|
+
]"
|
|
518
|
+
:height="200"
|
|
519
|
+
/>
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
</template>
|
|
523
|
+
</ComponentDemo>
|
|
524
|
+
|
|
525
|
+
### Many trajectories with low opacity
|
|
526
|
+
|
|
527
|
+
<ComponentDemo>
|
|
528
|
+
<LineChart
|
|
529
|
+
:series="Array.from({ length: 20 }, (_, i) => ({
|
|
530
|
+
data: Array.from({ length: 50 }, (_, t) => Math.max(0, 30 * Math.sin(t / 8) + (Math.random() - 0.5) * 15 + i * 0.5)),
|
|
531
|
+
color: '#0057b7',
|
|
532
|
+
}))"
|
|
533
|
+
:height="250"
|
|
534
|
+
:line-opacity="0.15"
|
|
535
|
+
x-label="Days"
|
|
536
|
+
y-label="Incidence"
|
|
537
|
+
/>
|
|
538
|
+
|
|
539
|
+
<template #code>
|
|
540
|
+
|
|
541
|
+
```vue
|
|
542
|
+
<LineChart
|
|
543
|
+
:series="trajectories"
|
|
544
|
+
:height="250"
|
|
545
|
+
:line-opacity="0.15"
|
|
546
|
+
x-label="Days"
|
|
547
|
+
y-label="Incidence"
|
|
548
|
+
/>
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
</template>
|
|
552
|
+
</ComponentDemo>
|
|
553
|
+
|
|
554
|
+
### Blend mode
|
|
555
|
+
|
|
556
|
+
Set `blendMode` on a `Series` to apply a CSS `mix-blend-mode` to that
|
|
557
|
+
series' line (and dots, if enabled). Useful when two thick or
|
|
558
|
+
high-contrast lines overlap — `"multiply"` darkens the crossing point
|
|
559
|
+
on light backgrounds, `"screen"` lightens on dark, `"difference"`
|
|
560
|
+
inverts. The white `outline`, if any, is unaffected.
|
|
561
|
+
|
|
562
|
+
<ComponentDemo>
|
|
563
|
+
<LineChart
|
|
564
|
+
:series="[
|
|
565
|
+
{
|
|
566
|
+
data: Array.from({ length: 30 }, (_, t) => 20 + 15 * Math.sin(t / 4)),
|
|
567
|
+
color: '#ef4444',
|
|
568
|
+
strokeWidth: 3,
|
|
569
|
+
blendMode: 'multiply',
|
|
570
|
+
legend: 'Strain A',
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
data: Array.from({ length: 30 }, (_, t) => 20 + 15 * Math.cos(t / 4)),
|
|
574
|
+
color: '#3b82f6',
|
|
575
|
+
strokeWidth: 3,
|
|
576
|
+
blendMode: 'multiply',
|
|
577
|
+
legend: 'Strain B',
|
|
578
|
+
},
|
|
579
|
+
]"
|
|
580
|
+
:height="240"
|
|
581
|
+
x-label="Day"
|
|
582
|
+
y-label="Incidence"
|
|
583
|
+
/>
|
|
584
|
+
|
|
585
|
+
<template #code>
|
|
586
|
+
|
|
587
|
+
```vue
|
|
588
|
+
<LineChart
|
|
589
|
+
:series="[
|
|
590
|
+
{
|
|
591
|
+
data: strainA,
|
|
592
|
+
color: '#ef4444',
|
|
593
|
+
strokeWidth: 3,
|
|
594
|
+
blendMode: 'multiply',
|
|
595
|
+
legend: 'Strain A',
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
data: strainB,
|
|
599
|
+
color: '#3b82f6',
|
|
600
|
+
strokeWidth: 3,
|
|
601
|
+
blendMode: 'multiply',
|
|
602
|
+
legend: 'Strain B',
|
|
603
|
+
},
|
|
604
|
+
]"
|
|
605
|
+
:height="240"
|
|
606
|
+
x-label="Day"
|
|
607
|
+
y-label="Incidence"
|
|
608
|
+
/>
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
</template>
|
|
612
|
+
</ComponentDemo>
|
|
613
|
+
|
|
614
|
+
### Grid lines
|
|
615
|
+
|
|
616
|
+
<ComponentDemo>
|
|
617
|
+
<LineChart
|
|
618
|
+
:series="[
|
|
619
|
+
{ data: [0, 10, 25, 45, 60, 55, 40, 20, 8], color: '#fb7e38', strokeWidth: 3 },
|
|
620
|
+
{ data: [0, 5, 12, 20, 28, 25, 18, 10, 4], color: '#0057b7', strokeWidth: 3 },
|
|
621
|
+
]"
|
|
622
|
+
:height="200"
|
|
623
|
+
x-label="Weeks"
|
|
624
|
+
y-label="Incidence"
|
|
625
|
+
x-grid
|
|
626
|
+
y-grid
|
|
627
|
+
/>
|
|
628
|
+
|
|
629
|
+
<template #code>
|
|
630
|
+
|
|
631
|
+
```vue
|
|
632
|
+
<LineChart
|
|
633
|
+
:series="[
|
|
634
|
+
{
|
|
635
|
+
data: [0, 10, 25, 45, 60, 55, 40, 20, 8],
|
|
636
|
+
color: '#fb7e38',
|
|
637
|
+
strokeWidth: 3,
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
data: [0, 5, 12, 20, 28, 25, 18, 10, 4],
|
|
641
|
+
color: '#0057b7',
|
|
642
|
+
strokeWidth: 3,
|
|
643
|
+
},
|
|
644
|
+
]"
|
|
645
|
+
:height="200"
|
|
646
|
+
x-label="Weeks"
|
|
647
|
+
y-label="Incidence"
|
|
648
|
+
x-grid
|
|
649
|
+
y-grid
|
|
650
|
+
/>
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
</template>
|
|
654
|
+
</ComponentDemo>
|
|
655
|
+
|
|
656
|
+
### Dots
|
|
657
|
+
|
|
658
|
+
<ComponentDemo>
|
|
659
|
+
<LineChart
|
|
660
|
+
:series="[
|
|
661
|
+
{ data: [0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2], color: '#0057b7', strokeWidth: 2, dots: true, dotRadius: 4, dotFill: '#fff', dotStroke: '#0057b7' },
|
|
662
|
+
]"
|
|
663
|
+
:height="200"
|
|
664
|
+
x-label="Days"
|
|
665
|
+
y-label="Cases"
|
|
666
|
+
/>
|
|
667
|
+
|
|
668
|
+
<template #code>
|
|
669
|
+
|
|
670
|
+
```vue
|
|
671
|
+
<LineChart
|
|
672
|
+
:series="[
|
|
673
|
+
{
|
|
674
|
+
data: [0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2],
|
|
675
|
+
color: '#0057b7',
|
|
676
|
+
strokeWidth: 2,
|
|
677
|
+
dots: true,
|
|
678
|
+
dotRadius: 4,
|
|
679
|
+
dotFill: '#fff',
|
|
680
|
+
dotStroke: '#0057b7',
|
|
681
|
+
},
|
|
682
|
+
]"
|
|
683
|
+
:height="200"
|
|
684
|
+
x-label="Days"
|
|
685
|
+
y-label="Cases"
|
|
686
|
+
/>
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
</template>
|
|
690
|
+
</ComponentDemo>
|
|
691
|
+
|
|
692
|
+
### Confidence band
|
|
693
|
+
|
|
694
|
+
Use the `areas` prop to fill a band between two y-series — useful for
|
|
695
|
+
confidence intervals or min/max envelopes around a mean line. Each `Area`
|
|
696
|
+
takes parallel `upper` and `lower` arrays (and an optional `x` array, just
|
|
697
|
+
like `Series`). Set `legend` on an `Area` to include the band in the inline
|
|
698
|
+
legend; use `showInLegend: false` to hide a legend label without removing
|
|
699
|
+
the field from shared config. Set `blendMode` on an `Area` to apply a CSS
|
|
700
|
+
`mix-blend-mode` to its fill so overlapping bands combine their colors
|
|
701
|
+
(e.g. `"multiply"`) instead of the later one obscuring the earlier.
|
|
702
|
+
|
|
703
|
+
<ComponentDemo>
|
|
704
|
+
<LineChart
|
|
705
|
+
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
|
|
706
|
+
:areas="[
|
|
707
|
+
{
|
|
708
|
+
upper: [2, 8, 14, 22, 30, 38, 36, 28, 19, 11, 7],
|
|
709
|
+
lower: [0, 1, 3, 9, 15, 22, 21, 13, 6, 1, 0],
|
|
710
|
+
color: '#0057b7',
|
|
711
|
+
opacity: 0.15,
|
|
712
|
+
legend: '95% interval',
|
|
713
|
+
},
|
|
714
|
+
]"
|
|
715
|
+
:height="220"
|
|
716
|
+
x-label="Days"
|
|
717
|
+
y-label="Cases"
|
|
718
|
+
tooltip-trigger="hover"
|
|
719
|
+
/>
|
|
720
|
+
|
|
721
|
+
<template #code>
|
|
722
|
+
|
|
723
|
+
```vue
|
|
724
|
+
<LineChart
|
|
725
|
+
:data="mean"
|
|
726
|
+
:areas="[
|
|
727
|
+
{
|
|
728
|
+
upper: ci95Hi,
|
|
729
|
+
lower: ci95Lo,
|
|
730
|
+
color: '#0057b7',
|
|
731
|
+
opacity: 0.15,
|
|
732
|
+
legend: '95% interval',
|
|
733
|
+
},
|
|
734
|
+
]"
|
|
735
|
+
:height="220"
|
|
736
|
+
x-label="Days"
|
|
737
|
+
y-label="Cases"
|
|
738
|
+
tooltip-trigger="hover"
|
|
739
|
+
/>
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
</template>
|
|
743
|
+
</ComponentDemo>
|
|
744
|
+
|
|
745
|
+
### Area sections
|
|
746
|
+
|
|
747
|
+
Highlight a range of a series line by filling the area between the line and the x-axis. Labels are rendered below the chart and automatically stack when they overlap.
|
|
748
|
+
|
|
749
|
+
<ComponentDemo>
|
|
750
|
+
<LineChart
|
|
751
|
+
:series="[
|
|
752
|
+
{ data: [0, 2, 5, 12, 25, 45, 70, 100, 130, 155, 170], color: '#000', strokeWidth: 1, legend: 'No interventions' },
|
|
753
|
+
{ data: [0, 0, 0, 2, 8, 20, 40, 65, 90, 110, 120], color: '#999', strokeWidth: 1, dashed: true, legend: 'Interventions' },
|
|
754
|
+
]"
|
|
755
|
+
:area-sections="[
|
|
756
|
+
{ startIndex: 2, endIndex: 7, color: '#6366f1', strokeWidth: 0, legend: 'inline', label: 'Day 2–7', description: 'Rapid growth phase' },
|
|
757
|
+
{ seriesIndex: 0, startIndex: 5, endIndex: 9, color: '#f43f5e', label: 'Day 5–9', description: 'Mitigation period' },
|
|
758
|
+
]"
|
|
759
|
+
:height="250"
|
|
760
|
+
x-label="Days"
|
|
761
|
+
y-label="Cumulative count"
|
|
762
|
+
tooltip-trigger="hover"
|
|
763
|
+
:menu="false"
|
|
764
|
+
/>
|
|
765
|
+
|
|
766
|
+
<template #code>
|
|
767
|
+
|
|
768
|
+
```vue
|
|
769
|
+
<LineChart
|
|
770
|
+
:series="[
|
|
771
|
+
{
|
|
772
|
+
data: [0, 2, 5, 12, 25, 45, 70, 100, 130, 155, 170],
|
|
773
|
+
color: '#000',
|
|
774
|
+
strokeWidth: 1,
|
|
775
|
+
legend: 'No interventions',
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
data: [0, 0, 0, 2, 8, 20, 40, 65, 90, 110, 120],
|
|
779
|
+
color: '#999',
|
|
780
|
+
strokeWidth: 1,
|
|
781
|
+
dashed: true,
|
|
782
|
+
legend: 'Interventions',
|
|
783
|
+
},
|
|
784
|
+
]"
|
|
785
|
+
:area-sections="[
|
|
786
|
+
{
|
|
787
|
+
startIndex: 2,
|
|
788
|
+
endIndex: 7,
|
|
789
|
+
color: '#6366f1',
|
|
790
|
+
strokeWidth: 0,
|
|
791
|
+
legend: 'inline',
|
|
792
|
+
label: 'Day 2–7',
|
|
793
|
+
description: 'Rapid growth phase',
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
seriesIndex: 0,
|
|
797
|
+
startIndex: 5,
|
|
798
|
+
endIndex: 9,
|
|
799
|
+
color: '#f43f5e',
|
|
800
|
+
label: 'Day 5–9',
|
|
801
|
+
description: 'Mitigation period',
|
|
802
|
+
},
|
|
803
|
+
]"
|
|
804
|
+
:height="250"
|
|
805
|
+
x-label="Days"
|
|
806
|
+
y-label="Cumulative count"
|
|
807
|
+
tooltip-trigger="hover"
|
|
808
|
+
/>
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
</template>
|
|
812
|
+
</ComponentDemo>
|
|
813
|
+
|
|
814
|
+
### Annotations
|
|
815
|
+
|
|
816
|
+
Pin callouts to data points with `annotations`. Each annotation anchors at
|
|
817
|
+
`(x, y)` in data coordinates (the same x-space as the chart axis, so it
|
|
818
|
+
respects `xMin` and explicit `x` values), with a pixel
|
|
819
|
+
`offset: { x, y }` for the label position. Text supports `\n` for line
|
|
820
|
+
breaks. A curved
|
|
821
|
+
pointer line connects the anchor to the label, and the label gets an
|
|
822
|
+
outline stroke matching the background so it stays legible over series
|
|
823
|
+
lines.
|
|
824
|
+
|
|
825
|
+
<ComponentDemo>
|
|
826
|
+
<LineChart
|
|
827
|
+
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
|
|
828
|
+
:annotations="[
|
|
829
|
+
{ x: 5, y: 30, offset: { x: 24, y: -28 }, text: 'Peak\nDay 5' },
|
|
830
|
+
{ x: 0, y: 0, offset: { x: 28, y: -22 }, text: 'Onset' },
|
|
831
|
+
]"
|
|
832
|
+
:chart-padding="{ top: 40, right: 24 }"
|
|
833
|
+
:height="240"
|
|
834
|
+
x-label="Days"
|
|
835
|
+
y-label="Cases"
|
|
836
|
+
/>
|
|
837
|
+
|
|
838
|
+
<template #code>
|
|
839
|
+
|
|
840
|
+
```vue
|
|
841
|
+
<LineChart
|
|
842
|
+
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
|
|
843
|
+
:annotations="[
|
|
844
|
+
{ x: 5, y: 30, offset: { x: 24, y: -28 }, text: 'Peak\nDay 5' },
|
|
845
|
+
{ x: 0, y: 0, offset: { x: 28, y: -22 }, text: 'Onset' },
|
|
846
|
+
]"
|
|
847
|
+
:chart-padding="{ top: 40, right: 24 }"
|
|
848
|
+
:height="240"
|
|
849
|
+
x-label="Days"
|
|
850
|
+
y-label="Cases"
|
|
851
|
+
/>
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
</template>
|
|
855
|
+
</ComponentDemo>
|
|
856
|
+
|
|
857
|
+
Use `chart-padding` to reserve room outside the plot so annotations (or
|
|
858
|
+
any other overlay) don't clip the data area. It accepts a number (same on
|
|
859
|
+
all sides) or an object with `top`, `right`, `bottom`, `left`.
|
|
860
|
+
|
|
861
|
+
Annotation text supports a small set of inline markers:
|
|
862
|
+
|
|
863
|
+
- `**bold**` — bold
|
|
864
|
+
- `_italic_` — italic
|
|
865
|
+
- `\n` — line break
|
|
866
|
+
|
|
867
|
+
Markers compose (`**_bold italic_**`).
|
|
868
|
+
|
|
869
|
+
<ComponentDemo>
|
|
870
|
+
<LineChart
|
|
871
|
+
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
|
|
872
|
+
:annotations="[
|
|
873
|
+
{ x: 5, y: 30, offset: { x: 24, y: -28 }, text: '**Peak**\n_Day 5_' },
|
|
874
|
+
]"
|
|
875
|
+
:chart-padding="{ top: 40, right: 24 }"
|
|
876
|
+
:height="240"
|
|
877
|
+
x-label="Days"
|
|
878
|
+
y-label="Cases"
|
|
879
|
+
/>
|
|
880
|
+
|
|
881
|
+
<template #code>
|
|
882
|
+
|
|
883
|
+
```vue
|
|
884
|
+
<LineChart
|
|
885
|
+
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
|
|
886
|
+
:annotations="[
|
|
887
|
+
{ x: 5, y: 30, offset: { x: 24, y: -28 }, text: '**Peak**\n_Day 5_' },
|
|
888
|
+
]"
|
|
889
|
+
:chart-padding="{ top: 40, right: 24 }"
|
|
890
|
+
:height="240"
|
|
891
|
+
x-label="Days"
|
|
892
|
+
y-label="Cases"
|
|
893
|
+
/>
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
</template>
|
|
897
|
+
</ComponentDemo>
|
|
898
|
+
|
|
899
|
+
Multi-line callouts attach to whichever edge of the text faces the anchor:
|
|
900
|
+
text **above** the point connects to its bottom edge, text **below**
|
|
901
|
+
connects to its top, and text to the side connects to its near vertical
|
|
902
|
+
edge — so the pointer never cuts through the lines. The demo below anchors
|
|
903
|
+
the same two-line label at one point and fans it out in every direction.
|
|
904
|
+
|
|
905
|
+
<ComponentDemo>
|
|
906
|
+
<LineChart
|
|
907
|
+
:data="[2, 10, 7, 6, 8, 4, 9]"
|
|
908
|
+
:annotations="[
|
|
909
|
+
{ x: 3, y: 6, offset: { x: 0, y: -86 }, text: 'Cluster\ndetected' },
|
|
910
|
+
{ x: 3, y: 6, offset: { x: 62, y: -62 }, text: 'Cluster\ndetected' },
|
|
911
|
+
{ x: 3, y: 6, offset: { x: 96, y: 0 }, text: 'Cluster\ndetected' },
|
|
912
|
+
{ x: 3, y: 6, offset: { x: 62, y: 62 }, text: 'Cluster\ndetected' },
|
|
913
|
+
{ x: 3, y: 6, offset: { x: 0, y: 86 }, text: 'Cluster\ndetected' },
|
|
914
|
+
{ x: 3, y: 6, offset: { x: -62, y: 62 }, text: 'Cluster\ndetected' },
|
|
915
|
+
{ x: 3, y: 6, offset: { x: -96, y: 0 }, text: 'Cluster\ndetected' },
|
|
916
|
+
{ x: 3, y: 6, offset: { x: -62, y: -62 }, text: 'Cluster\ndetected' },
|
|
917
|
+
]"
|
|
918
|
+
:chart-padding="{ top: 70, right: 120, bottom: 50, left: 120 }"
|
|
919
|
+
:height="340"
|
|
920
|
+
x-label="Days"
|
|
921
|
+
y-label="Cases"
|
|
922
|
+
/>
|
|
923
|
+
|
|
924
|
+
<template #code>
|
|
925
|
+
|
|
926
|
+
```vue
|
|
927
|
+
<LineChart
|
|
928
|
+
:data="[2, 10, 7, 6, 8, 4, 9]"
|
|
929
|
+
:annotations="[
|
|
930
|
+
{ x: 3, y: 6, offset: { x: 0, y: -86 }, text: 'Cluster\ndetected' },
|
|
931
|
+
{ x: 3, y: 6, offset: { x: 62, y: -62 }, text: 'Cluster\ndetected' },
|
|
932
|
+
{ x: 3, y: 6, offset: { x: 96, y: 0 }, text: 'Cluster\ndetected' },
|
|
933
|
+
{ x: 3, y: 6, offset: { x: 62, y: 62 }, text: 'Cluster\ndetected' },
|
|
934
|
+
{ x: 3, y: 6, offset: { x: 0, y: 86 }, text: 'Cluster\ndetected' },
|
|
935
|
+
{ x: 3, y: 6, offset: { x: -62, y: 62 }, text: 'Cluster\ndetected' },
|
|
936
|
+
{ x: 3, y: 6, offset: { x: -96, y: 0 }, text: 'Cluster\ndetected' },
|
|
937
|
+
{ x: 3, y: 6, offset: { x: -62, y: -62 }, text: 'Cluster\ndetected' },
|
|
938
|
+
]"
|
|
939
|
+
:chart-padding="{ top: 70, right: 120, bottom: 50, left: 120 }"
|
|
940
|
+
:height="340"
|
|
941
|
+
x-label="Days"
|
|
942
|
+
y-label="Cases"
|
|
943
|
+
/>
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
</template>
|
|
947
|
+
</ComponentDemo>
|
|
948
|
+
|
|
949
|
+
```ts
|
|
950
|
+
interface ChartAnnotation {
|
|
951
|
+
x: number; // anchor in data coords (x-axis)
|
|
952
|
+
y: number; // anchor in data coords (y-axis)
|
|
953
|
+
text: string; // label text; \n produces line breaks
|
|
954
|
+
offset: { x: number; y: number }; // pixel offset from anchor to label
|
|
955
|
+
color?: string; // text / pointer color (default: currentColor)
|
|
956
|
+
fontSize?: number; // default: 13 (matches axis labels)
|
|
957
|
+
fontWeight?: string | number; // default: "normal"
|
|
958
|
+
outlineColor?: string; // background-matched outline (default: var(--color-bg-0, #fff))
|
|
959
|
+
outlineWidth?: number; // default: 3
|
|
960
|
+
align?: "left" | "center" | "right"; // default: derived from offset[0] sign
|
|
961
|
+
lineColor?: string; // connector-line color override (default: color)
|
|
962
|
+
lineWidth?: number; // default: 1
|
|
963
|
+
lineDash?: string | number | readonly number[]; // SVG stroke-dasharray
|
|
964
|
+
// default: "curved"
|
|
965
|
+
// "ruleX" / "ruleY" span the full plot on the named axis;
|
|
966
|
+
// "ruleUp" / "ruleDown" / "ruleFromLeft" / "ruleFromRight" run from an edge to the anchor.
|
|
967
|
+
pointer?:
|
|
968
|
+
| "curved"
|
|
969
|
+
| "straight"
|
|
970
|
+
| "none"
|
|
971
|
+
| "ruleX"
|
|
972
|
+
| "ruleY"
|
|
973
|
+
| "ruleUp"
|
|
974
|
+
| "ruleDown"
|
|
975
|
+
| "ruleFromLeft"
|
|
976
|
+
| "ruleFromRight";
|
|
977
|
+
arrow?: boolean; // triangle marker at the anchor end (default: true)
|
|
978
|
+
}
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
### Rules
|
|
982
|
+
|
|
983
|
+
Set `pointer` to one of the rule values to replace the curved /
|
|
984
|
+
straight connector with a straight line through the anchor:
|
|
985
|
+
|
|
986
|
+
- `"ruleX"` — vertical line spanning the plot height at the annotation's `x`.
|
|
987
|
+
- `"ruleY"` — horizontal line spanning the plot width at the annotation's `y`.
|
|
988
|
+
- `"ruleUp"` — vertical from the bottom edge up to the anchor.
|
|
989
|
+
- `"ruleDown"` — vertical from the top edge down to the anchor.
|
|
990
|
+
- `"ruleFromLeft"` — horizontal from the left edge in to the anchor.
|
|
991
|
+
- `"ruleFromRight"` — horizontal from the right edge in to the anchor.
|
|
992
|
+
|
|
993
|
+
`lineColor`, `lineWidth`, and `lineDash` style the line. The label is
|
|
994
|
+
positioned from the anchor as usual via `offset`.
|
|
995
|
+
|
|
996
|
+
<ComponentDemo>
|
|
997
|
+
<LineChart
|
|
998
|
+
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
|
|
999
|
+
:annotations="[
|
|
1000
|
+
{
|
|
1001
|
+
x: 5,
|
|
1002
|
+
y: 30,
|
|
1003
|
+
offset: { x: 8, y: 14 },
|
|
1004
|
+
text: 'Peak',
|
|
1005
|
+
pointer: 'ruleX',
|
|
1006
|
+
lineDash: '4 3',
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
x: 5,
|
|
1010
|
+
y: 30,
|
|
1011
|
+
offset: { x: -8, y: -6 },
|
|
1012
|
+
text: 'Max',
|
|
1013
|
+
align: 'right',
|
|
1014
|
+
pointer: 'ruleFromLeft',
|
|
1015
|
+
lineDash: '4 3',
|
|
1016
|
+
},
|
|
1017
|
+
]"
|
|
1018
|
+
:chart-padding="{ top: 24, right: 24 }"
|
|
1019
|
+
:height="240"
|
|
1020
|
+
x-label="Days"
|
|
1021
|
+
y-label="Cases"
|
|
1022
|
+
/>
|
|
1023
|
+
|
|
1024
|
+
<template #code>
|
|
1025
|
+
|
|
1026
|
+
```vue
|
|
1027
|
+
<LineChart
|
|
1028
|
+
:data="[0, 4, 8, 15, 22, 30, 28, 20, 12, 5, 2]"
|
|
1029
|
+
:annotations="[
|
|
1030
|
+
{
|
|
1031
|
+
x: 5,
|
|
1032
|
+
y: 30,
|
|
1033
|
+
offset: { x: 8, y: 14 },
|
|
1034
|
+
text: 'Peak',
|
|
1035
|
+
pointer: 'ruleX',
|
|
1036
|
+
lineDash: '4 3',
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
x: 5,
|
|
1040
|
+
y: 30,
|
|
1041
|
+
offset: { x: -8, y: -6 },
|
|
1042
|
+
text: 'Max',
|
|
1043
|
+
align: 'right',
|
|
1044
|
+
pointer: 'ruleFromLeft',
|
|
1045
|
+
lineDash: '4 3',
|
|
1046
|
+
},
|
|
1047
|
+
]"
|
|
1048
|
+
:chart-padding="{ top: 24, right: 24 }"
|
|
1049
|
+
:height="240"
|
|
1050
|
+
x-label="Days"
|
|
1051
|
+
y-label="Cases"
|
|
1052
|
+
/>
|
|
1053
|
+
```
|
|
1054
|
+
|
|
1055
|
+
</template>
|
|
1056
|
+
</ComponentDemo>
|
|
1057
|
+
|
|
1058
|
+
### Chart menu
|
|
1059
|
+
|
|
1060
|
+
The chart menu (top-right) leads with Fullscreen and then Save as SVG,
|
|
1061
|
+
Save as PNG, and Download CSV. Fullscreen grows the chart to fill the browser
|
|
1062
|
+
window so the plot has more room; while expanded the menu trigger is replaced
|
|
1063
|
+
by a close (✕) button — click it or press <kbd>Esc</kbd> to return to the
|
|
1064
|
+
inline size. Set `menu="false"` to hide the trigger entirely.
|
|
1065
|
+
|
|
1066
|
+
While expanded the chart is teleported to `<body>` so it reliably covers the
|
|
1067
|
+
viewport even when an ancestor uses `transform`, `filter`, `contain`, or
|
|
1068
|
+
establishes a stacking context. If your app doesn't mount at the document root
|
|
1069
|
+
(e.g. inside a shadow root or a dedicated overlay container), point
|
|
1070
|
+
`fullscreenTarget` at a CSS selector or element to teleport there instead.
|
|
1071
|
+
|
|
1072
|
+
### Custom CSV download
|
|
1073
|
+
|
|
1074
|
+
By default, the Download CSV menu item exports the chart series as CSV. Use
|
|
1075
|
+
the `csv` prop to supply your own content (for example, to include original
|
|
1076
|
+
dates, categorical labels, or extra columns that aren't plotted). Use
|
|
1077
|
+
`filename` to control the download filename (shared by SVG, PNG and CSV).
|
|
1078
|
+
|
|
1079
|
+
Pass `download-link` to also render a plain text link below the chart — set
|
|
1080
|
+
it to `true` for the default label, or pass a string to customize it. Use
|
|
1081
|
+
`download-button` instead to render a styled `<button>` (with the class
|
|
1082
|
+
`line-chart-download-button`, available for custom CSS) in place of the
|
|
1083
|
+
link.
|
|
1084
|
+
|
|
1085
|
+
<ComponentDemo>
|
|
1086
|
+
<LineChart
|
|
1087
|
+
:data="[10, 22, 35, 48]"
|
|
1088
|
+
:height="200"
|
|
1089
|
+
filename="weekly-cases"
|
|
1090
|
+
:csv="'week,cases\n2024-W01,10\n2024-W02,22\n2024-W03,35\n2024-W04,48'"
|
|
1091
|
+
x-label="Week"
|
|
1092
|
+
y-label="Cases"
|
|
1093
|
+
download-link="Download weekly cases (CSV)"
|
|
1094
|
+
/>
|
|
1095
|
+
|
|
1096
|
+
<template #code>
|
|
1097
|
+
|
|
1098
|
+
```vue
|
|
1099
|
+
<LineChart
|
|
1100
|
+
:data="[10, 22, 35, 48]"
|
|
1101
|
+
:height="200"
|
|
1102
|
+
filename="weekly-cases"
|
|
1103
|
+
:csv="`week,cases
|
|
1104
|
+
2024-W01,10
|
|
1105
|
+
2024-W02,22
|
|
1106
|
+
2024-W03,35
|
|
1107
|
+
2024-W04,48`"
|
|
1108
|
+
x-label="Week"
|
|
1109
|
+
y-label="Cases"
|
|
1110
|
+
download-link="Download weekly cases (CSV)"
|
|
1111
|
+
/>
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
</template>
|
|
1115
|
+
</ComponentDemo>
|
|
1116
|
+
|
|
1117
|
+
`csv` also accepts a function, which is useful for deferring serialization
|
|
1118
|
+
until the user clicks Download:
|
|
1119
|
+
|
|
1120
|
+
```vue
|
|
1121
|
+
<LineChart :data="cases" :csv="() => buildCsv(cases, dates)" />
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
## Accessibility
|
|
1125
|
+
|
|
1126
|
+
The chart's `<svg>` is exposed as a single labeled image so screen readers
|
|
1127
|
+
announce one accessible name instead of wandering through the individual marks.
|
|
1128
|
+
When the chart has a `title`, the `<svg>` gets `role="img"` and an `aria-label`
|
|
1129
|
+
set to the title. The menu and download controls live outside the `<svg>`, so
|
|
1130
|
+
they stay reachable regardless.
|
|
1131
|
+
|
|
1132
|
+
Set `ariaLabel` to give screen readers a fuller summary than the visible title
|
|
1133
|
+
(it overrides `title` for the accessible name only):
|
|
1134
|
+
|
|
1135
|
+
```vue
|
|
1136
|
+
<LineChart
|
|
1137
|
+
:data="cases"
|
|
1138
|
+
title="Daily ED visits"
|
|
1139
|
+
aria-label="Daily ED visits over 11 days, rising from 12 to a peak of 48"
|
|
1140
|
+
/>
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
Pass `role` to override the default `"img"` (e.g. `role="figure"`, or a role of
|
|
1144
|
+
your own).
|
|
1145
|
+
|
|
1146
|
+
<!--@include: ./_api/line-chart.md-->
|
|
1147
|
+
|
|
1148
|
+
### Data
|
|
1149
|
+
|
|
1150
|
+
`data`, `series[].data`, and `areas[].upper`/`lower` accept a plain
|
|
1151
|
+
`number[]` or any standard numeric typed array (`Float64Array`,
|
|
1152
|
+
`Int32Array`, etc.). This lets you pass the output of
|
|
1153
|
+
`ModelOutput.column()` directly — no `Array.from(...)` copy is needed:
|
|
1154
|
+
|
|
1155
|
+
```vue
|
|
1156
|
+
<LineChart :data="outputs.series.column('values')" />
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
```ts
|
|
1160
|
+
type LineChartData =
|
|
1161
|
+
| readonly number[]
|
|
1162
|
+
| Float64Array
|
|
1163
|
+
| Float32Array
|
|
1164
|
+
| Int32Array
|
|
1165
|
+
| Uint32Array
|
|
1166
|
+
| Int16Array
|
|
1167
|
+
| Uint16Array
|
|
1168
|
+
| Int8Array
|
|
1169
|
+
| Uint8Array
|
|
1170
|
+
| Uint8ClampedArray;
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
### Series
|
|
1174
|
+
|
|
1175
|
+
```ts
|
|
1176
|
+
interface Series {
|
|
1177
|
+
y?: LineChartData; // y-values (preferred)
|
|
1178
|
+
data?: LineChartData; // y-values (alternative name; one of y/data must be set)
|
|
1179
|
+
x?: LineChartData; // optional parallel x-values
|
|
1180
|
+
color?: string;
|
|
1181
|
+
dashed?: boolean;
|
|
1182
|
+
strokeWidth?: number;
|
|
1183
|
+
opacity?: number;
|
|
1184
|
+
line?: boolean;
|
|
1185
|
+
outline?: boolean; // page-colored stroke drawn behind the line
|
|
1186
|
+
outlineColor?: string; // outline stroke color (default: var(--color-bg-0, #fff))
|
|
1187
|
+
outlineWidth?: number; // extra width added to strokeWidth (default: 4)
|
|
1188
|
+
dots?: boolean;
|
|
1189
|
+
dotRadius?: number;
|
|
1190
|
+
dotFill?: string;
|
|
1191
|
+
dotStroke?: string;
|
|
1192
|
+
}
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
### Area
|
|
1196
|
+
|
|
1197
|
+
```ts
|
|
1198
|
+
interface Area {
|
|
1199
|
+
upper: LineChartData;
|
|
1200
|
+
lower: LineChartData;
|
|
1201
|
+
x?: LineChartData; // optional parallel x-values
|
|
1202
|
+
color?: string;
|
|
1203
|
+
opacity?: number;
|
|
1204
|
+
legend?: string; // inline legend label
|
|
1205
|
+
showInLegend?: boolean; // default true when legend is set
|
|
1206
|
+
blendMode?: BlendMode; // CSS mix-blend-mode for the fill
|
|
1207
|
+
}
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
### AreaSection
|
|
1211
|
+
|
|
1212
|
+
```ts
|
|
1213
|
+
interface AreaSection {
|
|
1214
|
+
seriesIndex?: number; // omit for full-height fill
|
|
1215
|
+
startIndex: number;
|
|
1216
|
+
endIndex: number;
|
|
1217
|
+
color?: string;
|
|
1218
|
+
opacity?: number; // default: 0.15
|
|
1219
|
+
label?: string;
|
|
1220
|
+
description?: string;
|
|
1221
|
+
strokeWidth?: number; // default: 2
|
|
1222
|
+
dashed?: boolean;
|
|
1223
|
+
legend?: "inline" | "below" | false; // default: "below"
|
|
1224
|
+
}
|
|
1225
|
+
```
|