@cfasim-ui/docs 0.4.13 → 0.4.15
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/charts/BarChart/BarChart.md +222 -4
- package/charts/BarChart/BarChart.vue +292 -4
- package/charts/LineChart/LineChart.md +60 -0
- package/charts/LineChart/LineChart.vue +14 -14
- package/charts/_shared/chartProps.ts +53 -0
- package/charts/_shared/index.ts +2 -0
- package/charts/index.ts +2 -0
- package/index.json +2 -1
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# BarChart
|
|
2
2
|
|
|
3
|
-
A responsive SVG bar chart supporting single, grouped,
|
|
4
|
-
either vertical (column) or horizontal orientation. Shares axis,
|
|
5
|
-
menu, and CSV export wiring with [`LineChart`](./line-chart).
|
|
3
|
+
A responsive SVG bar chart supporting single, grouped, stacked, and overlay
|
|
4
|
+
series in either vertical (column) or horizontal orientation. Shares axis,
|
|
5
|
+
tooltip, menu, and CSV export wiring with [`LineChart`](./line-chart).
|
|
6
6
|
|
|
7
7
|
## Examples
|
|
8
8
|
|
|
@@ -100,6 +100,101 @@ menu, and CSV export wiring with [`LineChart`](./line-chart).
|
|
|
100
100
|
</template>
|
|
101
101
|
</ComponentDemo>
|
|
102
102
|
|
|
103
|
+
### Overlay series
|
|
104
|
+
|
|
105
|
+
`layout="overlay"` draws each series at full group width from the shared
|
|
106
|
+
baseline, painting later series on top of earlier ones. Use it to compare
|
|
107
|
+
a backdrop value (e.g. target, capacity, prior period) against a shorter
|
|
108
|
+
foreground value sharing the same axis.
|
|
109
|
+
|
|
110
|
+
Series render in the order they appear in `series`, so put the taller
|
|
111
|
+
backdrop first and the shorter foreground after. Use `opacity`, a
|
|
112
|
+
distinct color, or a per-series `blendMode` (see below) if bars might
|
|
113
|
+
overlap fully.
|
|
114
|
+
|
|
115
|
+
<ComponentDemo>
|
|
116
|
+
<BarChart
|
|
117
|
+
:series="[
|
|
118
|
+
{ data: [40, 40, 40, 40], legend: 'Target', color: '#d4d4d8' },
|
|
119
|
+
{ data: [22, 35, 28, 38], legend: 'Actual', color: '#0057b7' },
|
|
120
|
+
]"
|
|
121
|
+
:categories="['Q1', 'Q2', 'Q3', 'Q4']"
|
|
122
|
+
layout="overlay"
|
|
123
|
+
:height="220"
|
|
124
|
+
y-label="Doses (thousands)"
|
|
125
|
+
tooltip-trigger="hover"
|
|
126
|
+
/>
|
|
127
|
+
|
|
128
|
+
<template #code>
|
|
129
|
+
|
|
130
|
+
```vue
|
|
131
|
+
<BarChart
|
|
132
|
+
:series="[
|
|
133
|
+
{ data: [40, 40, 40, 40], legend: 'Target', color: '#d4d4d8' },
|
|
134
|
+
{ data: [22, 35, 28, 38], legend: 'Actual', color: '#0057b7' },
|
|
135
|
+
]"
|
|
136
|
+
:categories="['Q1', 'Q2', 'Q3', 'Q4']"
|
|
137
|
+
layout="overlay"
|
|
138
|
+
:height="220"
|
|
139
|
+
y-label="Doses (thousands)"
|
|
140
|
+
tooltip-trigger="hover"
|
|
141
|
+
/>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
</template>
|
|
145
|
+
</ComponentDemo>
|
|
146
|
+
|
|
147
|
+
### Blend mode
|
|
148
|
+
|
|
149
|
+
Set `blendMode` on a series to apply a CSS `mix-blend-mode` to every
|
|
150
|
+
bar in that series. Useful with `layout="overlay"` (or just two
|
|
151
|
+
overlapping grouped bars) to combine colors instead of one obscuring
|
|
152
|
+
the other. Common picks: `"multiply"` (darkens light fills where bars
|
|
153
|
+
overlap), `"screen"` (lightens dark fills), `"difference"` (high
|
|
154
|
+
contrast).
|
|
155
|
+
|
|
156
|
+
<ComponentDemo>
|
|
157
|
+
<BarChart
|
|
158
|
+
:series="[
|
|
159
|
+
{ data: [22, 35, 28, 38], legend: 'Treatment A', color: '#ef4444', blendMode: 'multiply' },
|
|
160
|
+
{ data: [30, 24, 36, 32], legend: 'Treatment B', color: '#3b82f6', blendMode: 'multiply' },
|
|
161
|
+
]"
|
|
162
|
+
:categories="['Q1', 'Q2', 'Q3', 'Q4']"
|
|
163
|
+
layout="overlay"
|
|
164
|
+
:height="220"
|
|
165
|
+
y-label="Cases avoided"
|
|
166
|
+
tooltip-trigger="hover"
|
|
167
|
+
/>
|
|
168
|
+
|
|
169
|
+
<template #code>
|
|
170
|
+
|
|
171
|
+
```vue
|
|
172
|
+
<BarChart
|
|
173
|
+
:series="[
|
|
174
|
+
{
|
|
175
|
+
data: [22, 35, 28, 38],
|
|
176
|
+
legend: 'Treatment A',
|
|
177
|
+
color: '#ef4444',
|
|
178
|
+
blendMode: 'multiply',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
data: [30, 24, 36, 32],
|
|
182
|
+
legend: 'Treatment B',
|
|
183
|
+
color: '#3b82f6',
|
|
184
|
+
blendMode: 'multiply',
|
|
185
|
+
},
|
|
186
|
+
]"
|
|
187
|
+
:categories="['Q1', 'Q2', 'Q3', 'Q4']"
|
|
188
|
+
layout="overlay"
|
|
189
|
+
:height="220"
|
|
190
|
+
y-label="Cases avoided"
|
|
191
|
+
tooltip-trigger="hover"
|
|
192
|
+
/>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
</template>
|
|
196
|
+
</ComponentDemo>
|
|
197
|
+
|
|
103
198
|
### Horizontal orientation
|
|
104
199
|
|
|
105
200
|
<ComponentDemo>
|
|
@@ -208,6 +303,54 @@ LineChart's `x-tick-format` (e.g. `"iso"`, `"month-year"`, an
|
|
|
208
303
|
`Intl.DateTimeFormatOptions` object, or a `(ms, unit?) => string`
|
|
209
304
|
function).
|
|
210
305
|
|
|
306
|
+
### Crowded categorical labels
|
|
307
|
+
|
|
308
|
+
Non-date categorical labels are also thinned when they don't all fit:
|
|
309
|
+
the chart estimates label width from character count and font size,
|
|
310
|
+
then strides through the categories so adjacent labels never overlap.
|
|
311
|
+
Every bar still renders — only the tick labels are skipped.
|
|
312
|
+
|
|
313
|
+
<ComponentDemo>
|
|
314
|
+
<BarChart
|
|
315
|
+
data-testid="thinned-bar-chart"
|
|
316
|
+
:data="[3, 5, 8, 12, 18, 24, 31, 27, 22, 16, 11, 7, 4, 2]"
|
|
317
|
+
:categories="['2.11', '2.33', '2.56', '2.78', '3.00', '3.22', '3.44', '3.67', '3.89', '4.11', '4.33', '4.56', '4.78', '5.00']"
|
|
318
|
+
:width="380"
|
|
319
|
+
:height="220"
|
|
320
|
+
x-label="R₀"
|
|
321
|
+
y-label="Frequency"
|
|
322
|
+
/>
|
|
323
|
+
|
|
324
|
+
<template #code>
|
|
325
|
+
|
|
326
|
+
```vue
|
|
327
|
+
<BarChart
|
|
328
|
+
:data="[3, 5, 8, 12, 18, 24, 31, 27, 22, 16, 11, 7, 4, 2]"
|
|
329
|
+
:categories="[
|
|
330
|
+
'2.11',
|
|
331
|
+
'2.33',
|
|
332
|
+
'2.56',
|
|
333
|
+
'2.78',
|
|
334
|
+
'3.00',
|
|
335
|
+
'3.22',
|
|
336
|
+
'3.44',
|
|
337
|
+
'3.67',
|
|
338
|
+
'3.89',
|
|
339
|
+
'4.11',
|
|
340
|
+
'4.33',
|
|
341
|
+
'4.56',
|
|
342
|
+
'4.78',
|
|
343
|
+
'5.00',
|
|
344
|
+
]"
|
|
345
|
+
:height="220"
|
|
346
|
+
x-label="R₀"
|
|
347
|
+
y-label="Frequency"
|
|
348
|
+
/>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
</template>
|
|
352
|
+
</ComponentDemo>
|
|
353
|
+
|
|
211
354
|
### Logarithmic value axis
|
|
212
355
|
|
|
213
356
|
Set `value-scale-type="log"` to switch the value axis to base-10 log
|
|
@@ -242,6 +385,80 @@ top still represents the sum.
|
|
|
242
385
|
</template>
|
|
243
386
|
</ComponentDemo>
|
|
244
387
|
|
|
388
|
+
### Summary line overlay
|
|
389
|
+
|
|
390
|
+
Layer a curve (KDE, rolling mean, target) on top of the bars with
|
|
391
|
+
`summary-lines`. Each line scales to **its own value extent** —
|
|
392
|
+
independent of the bar axis — so a probability-density curve in
|
|
393
|
+
`[0, 0.02]` and a histogram in `[0, 500]` can share the same chart.
|
|
394
|
+
|
|
395
|
+
<ComponentDemo>
|
|
396
|
+
<BarChart
|
|
397
|
+
:data="[3, 5, 8, 12, 18, 24, 31, 27, 22, 16, 11, 7, 4, 2]"
|
|
398
|
+
:categories="['2.11', '2.33', '2.56', '2.78', '3.00', '3.22', '3.44', '3.67', '3.89', '4.11', '4.33', '4.56', '4.78', '5.00']"
|
|
399
|
+
:summary-lines="[
|
|
400
|
+
{
|
|
401
|
+
data: [0.05, 0.10, 0.20, 0.40, 0.75, 1.10, 1.32, 1.20, 0.92, 0.60, 0.32, 0.16, 0.07, 0.03],
|
|
402
|
+
color: '#ef4444',
|
|
403
|
+
strokeWidth: 2,
|
|
404
|
+
legend: 'KDE',
|
|
405
|
+
},
|
|
406
|
+
]"
|
|
407
|
+
:width="380"
|
|
408
|
+
:height="240"
|
|
409
|
+
x-label="R₀"
|
|
410
|
+
y-label="Frequency"
|
|
411
|
+
/>
|
|
412
|
+
|
|
413
|
+
<template #code>
|
|
414
|
+
|
|
415
|
+
```vue
|
|
416
|
+
<BarChart
|
|
417
|
+
:data="[3, 5, 8, 12, 18, 24, 31, 27, 22, 16, 11, 7, 4, 2]"
|
|
418
|
+
:categories="[
|
|
419
|
+
'2.11',
|
|
420
|
+
'2.33',
|
|
421
|
+
'2.56',
|
|
422
|
+
'2.78',
|
|
423
|
+
'3.00',
|
|
424
|
+
'3.22',
|
|
425
|
+
'3.44',
|
|
426
|
+
'3.67',
|
|
427
|
+
'3.89',
|
|
428
|
+
'4.11',
|
|
429
|
+
'4.33',
|
|
430
|
+
'4.56',
|
|
431
|
+
'4.78',
|
|
432
|
+
'5.00',
|
|
433
|
+
]"
|
|
434
|
+
:summary-lines="[
|
|
435
|
+
{
|
|
436
|
+
data: [
|
|
437
|
+
0.05, 0.1, 0.2, 0.4, 0.75, 1.1, 1.32, 1.2, 0.92, 0.6, 0.32, 0.16, 0.07,
|
|
438
|
+
0.03,
|
|
439
|
+
],
|
|
440
|
+
color: '#ef4444',
|
|
441
|
+
strokeWidth: 2,
|
|
442
|
+
legend: 'KDE',
|
|
443
|
+
},
|
|
444
|
+
]"
|
|
445
|
+
:height="240"
|
|
446
|
+
x-label="R₀"
|
|
447
|
+
y-label="Frequency"
|
|
448
|
+
/>
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
</template>
|
|
452
|
+
</ComponentDemo>
|
|
453
|
+
|
|
454
|
+
Each line accepts `color`, `strokeWidth`, `dashed`, `opacity`,
|
|
455
|
+
`blendMode`, `dots`, and `dotRadius` for styling — same vocabulary as
|
|
456
|
+
`LineChart` series (both extend `LineMarkStyle`). Override the line's
|
|
457
|
+
extent with `valueMin` / `valueMax`. Pass `x: number[]` (in
|
|
458
|
+
category-index space — `0` is the first category center, fractional
|
|
459
|
+
values land between) to plot at custom positions; useful when the
|
|
460
|
+
summary samples are denser or sparser than the histogram bins.
|
|
461
|
+
|
|
245
462
|
### Annotations
|
|
246
463
|
|
|
247
464
|
Pin callouts to specific bars with `annotations`. `x` is the category
|
|
@@ -381,9 +598,10 @@ anchor. `lineColor`, `lineWidth`, and `lineDash` style the line.
|
|
|
381
598
|
| `data` | `BarChartData` | No | — |
|
|
382
599
|
| `y` | `BarChartData` | No | — |
|
|
383
600
|
| `series` | `BarSeries[]` | No | — |
|
|
601
|
+
| `summaryLines` | `BarSummaryLine[]` | No | — |
|
|
384
602
|
| `categories` | `readonly (string \| Date)[]` | No | — |
|
|
385
603
|
| `orientation` | `"vertical" \| "horizontal"` | No | `"vertical"` |
|
|
386
|
-
| `layout` | `"grouped" \| "stacked"` | No | `"grouped"` |
|
|
604
|
+
| `layout` | `"grouped" \| "stacked" \| "overlay"` | No | `"grouped"` |
|
|
387
605
|
| `valueMin` | `number` | No | `0` |
|
|
388
606
|
| `valueScaleType` | `"linear" \| "log"` | No | `"linear"` |
|
|
389
607
|
| `valueTicks` | `number \| number[]` | No | — |
|
|
@@ -32,6 +32,8 @@ import {
|
|
|
32
32
|
type ChartHoverPayload,
|
|
33
33
|
type ChartTooltipBaseProps,
|
|
34
34
|
type ChartTooltipValue,
|
|
35
|
+
type BlendMode,
|
|
36
|
+
type LineMarkStyle,
|
|
35
37
|
} from "../_shared/index.js";
|
|
36
38
|
|
|
37
39
|
export type BarChartData = ChartData;
|
|
@@ -42,6 +44,12 @@ export interface BarSeries {
|
|
|
42
44
|
data?: BarChartData;
|
|
43
45
|
color?: string;
|
|
44
46
|
opacity?: number;
|
|
47
|
+
/**
|
|
48
|
+
* CSS `mix-blend-mode` applied to each bar in this series. Lets
|
|
49
|
+
* overlapping bars (e.g. `layout="overlay"`) combine their colors
|
|
50
|
+
* instead of one obscuring the other.
|
|
51
|
+
*/
|
|
52
|
+
blendMode?: BlendMode;
|
|
45
53
|
/** Label shown in the inline legend. */
|
|
46
54
|
legend?: string;
|
|
47
55
|
/**
|
|
@@ -56,6 +64,38 @@ export interface BarSeries {
|
|
|
56
64
|
showInTooltip?: boolean;
|
|
57
65
|
}
|
|
58
66
|
|
|
67
|
+
/**
|
|
68
|
+
* A line overlay drawn on top of the bars — e.g. a KDE over a histogram,
|
|
69
|
+
* a rolling mean, or any other summary curve. Each line scales to its
|
|
70
|
+
* own value extent (independent of the bars), so a probability-density
|
|
71
|
+
* curve in `[0, 0.02]` and a count histogram in `[0, 500]` can coexist.
|
|
72
|
+
*
|
|
73
|
+
* Sample the curve densely on the input side — `summaryLines` connects
|
|
74
|
+
* points with straight segments (matching matplotlib / seaborn `kde=True`).
|
|
75
|
+
*
|
|
76
|
+
* Shares visual styling (`color`, `strokeWidth`, `dashed`, `opacity`,
|
|
77
|
+
* `blendMode`, `dots`, `dotRadius`, `legend`, `showInLegend`) with
|
|
78
|
+
* `LineChart`'s `Series` via `LineMarkStyle`.
|
|
79
|
+
*/
|
|
80
|
+
export interface BarSummaryLine extends LineMarkStyle {
|
|
81
|
+
/** Y values along the line. */
|
|
82
|
+
data: BarChartData;
|
|
83
|
+
/**
|
|
84
|
+
* X positions in category-index space (0 = first category center,
|
|
85
|
+
* `categories.length - 1` = last). Fractional values land between
|
|
86
|
+
* category centers. When omitted, points plot at successive category
|
|
87
|
+
* centers — one point per `data` entry.
|
|
88
|
+
*/
|
|
89
|
+
x?: BarChartData;
|
|
90
|
+
/**
|
|
91
|
+
* Override the line's value-axis floor. When unset, defaults to the
|
|
92
|
+
* line's own min (clamped to 0 when all data is non-negative).
|
|
93
|
+
*/
|
|
94
|
+
valueMin?: number;
|
|
95
|
+
/** Override the line's value-axis ceiling. Defaults to the line's own max. */
|
|
96
|
+
valueMax?: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
59
99
|
interface BarChartProps extends ChartCommonProps {
|
|
60
100
|
/** Single-series values. Equivalent to `y`. */
|
|
61
101
|
data?: BarChartData;
|
|
@@ -63,6 +103,12 @@ interface BarChartProps extends ChartCommonProps {
|
|
|
63
103
|
y?: BarChartData;
|
|
64
104
|
/** Multi-series mode. Each series has its own values. */
|
|
65
105
|
series?: BarSeries[];
|
|
106
|
+
/**
|
|
107
|
+
* Line overlays drawn on top of the bars (e.g. a KDE, a rolling mean,
|
|
108
|
+
* or any summary curve). Each line scales to its own value extent —
|
|
109
|
+
* unrelated to the bar value axis. See `BarSummaryLine`.
|
|
110
|
+
*/
|
|
111
|
+
summaryLines?: BarSummaryLine[];
|
|
66
112
|
/**
|
|
67
113
|
* Category labels for the categorical axis. Length should match the
|
|
68
114
|
* longest series. When omitted, indices (0, 1, 2, ...) are used.
|
|
@@ -73,8 +119,14 @@ interface BarChartProps extends ChartCommonProps {
|
|
|
73
119
|
categories?: readonly (string | Date)[];
|
|
74
120
|
/** "vertical" (default, aka column) draws upright bars; "horizontal" draws sideways. */
|
|
75
121
|
orientation?: "vertical" | "horizontal";
|
|
76
|
-
/**
|
|
77
|
-
|
|
122
|
+
/**
|
|
123
|
+
* "grouped" (default) places series side-by-side; "stacked" stacks
|
|
124
|
+
* them; "overlay" draws each series at full group width from the
|
|
125
|
+
* shared baseline, painting later series on top of earlier ones.
|
|
126
|
+
* In overlay mode set `opacity` per series, or order series so the
|
|
127
|
+
* tallest renders first, so bars behind remain visible.
|
|
128
|
+
*/
|
|
129
|
+
layout?: "grouped" | "stacked" | "overlay";
|
|
78
130
|
/** Force the value axis to start at this value or lower (default 0). */
|
|
79
131
|
valueMin?: number;
|
|
80
132
|
/**
|
|
@@ -148,6 +200,7 @@ type ResolvedSeries = {
|
|
|
148
200
|
data: BarChartData;
|
|
149
201
|
color?: string;
|
|
150
202
|
opacity?: number;
|
|
203
|
+
blendMode?: BlendMode;
|
|
151
204
|
legend?: string;
|
|
152
205
|
showInLegend?: boolean;
|
|
153
206
|
showInTooltip?: boolean;
|
|
@@ -158,6 +211,7 @@ function resolveSeries(s: BarSeries): ResolvedSeries {
|
|
|
158
211
|
data: s.y ?? s.data ?? EMPTY_DATA,
|
|
159
212
|
color: s.color,
|
|
160
213
|
opacity: s.opacity,
|
|
214
|
+
blendMode: s.blendMode,
|
|
161
215
|
legend: s.legend,
|
|
162
216
|
showInLegend: s.showInLegend,
|
|
163
217
|
showInTooltip: s.showInTooltip,
|
|
@@ -334,6 +388,7 @@ interface BarRect {
|
|
|
334
388
|
h: number;
|
|
335
389
|
color: string;
|
|
336
390
|
opacity: number;
|
|
391
|
+
blendMode?: BlendMode;
|
|
337
392
|
value: number;
|
|
338
393
|
categoryIndex: number;
|
|
339
394
|
seriesIndex: number;
|
|
@@ -351,6 +406,7 @@ function makeBar(
|
|
|
351
406
|
span: number,
|
|
352
407
|
color: string,
|
|
353
408
|
opacity: number,
|
|
409
|
+
blendMode: BlendMode | undefined,
|
|
354
410
|
value: number,
|
|
355
411
|
categoryIndex: number,
|
|
356
412
|
seriesIndex: number,
|
|
@@ -365,6 +421,7 @@ function makeBar(
|
|
|
365
421
|
h: size,
|
|
366
422
|
color,
|
|
367
423
|
opacity,
|
|
424
|
+
blendMode,
|
|
368
425
|
value,
|
|
369
426
|
categoryIndex,
|
|
370
427
|
seriesIndex,
|
|
@@ -377,6 +434,7 @@ function makeBar(
|
|
|
377
434
|
h: span,
|
|
378
435
|
color,
|
|
379
436
|
opacity,
|
|
437
|
+
blendMode,
|
|
380
438
|
value,
|
|
381
439
|
categoryIndex,
|
|
382
440
|
seriesIndex,
|
|
@@ -414,6 +472,7 @@ const bars = computed<BarRect[]>(() => {
|
|
|
414
472
|
group,
|
|
415
473
|
series.color ?? defaultColor(s),
|
|
416
474
|
series.opacity ?? 1,
|
|
475
|
+
series.blendMode,
|
|
417
476
|
raw,
|
|
418
477
|
i,
|
|
419
478
|
s,
|
|
@@ -422,6 +481,26 @@ const bars = computed<BarRect[]>(() => {
|
|
|
422
481
|
if (raw >= 0) posCursor = top;
|
|
423
482
|
else negCursor = top;
|
|
424
483
|
}
|
|
484
|
+
} else if (props.layout === "overlay") {
|
|
485
|
+
for (let s = 0; s < k; s++) {
|
|
486
|
+
const series = seriesList[s];
|
|
487
|
+
const raw = Number(series.data[i] ?? NaN);
|
|
488
|
+
if (!isFinite(raw)) continue;
|
|
489
|
+
out.push(
|
|
490
|
+
makeBar(
|
|
491
|
+
baseline,
|
|
492
|
+
valuePixel(raw),
|
|
493
|
+
groupStart,
|
|
494
|
+
group,
|
|
495
|
+
series.color ?? defaultColor(s),
|
|
496
|
+
series.opacity ?? 1,
|
|
497
|
+
series.blendMode,
|
|
498
|
+
raw,
|
|
499
|
+
i,
|
|
500
|
+
s,
|
|
501
|
+
),
|
|
502
|
+
);
|
|
503
|
+
}
|
|
425
504
|
} else {
|
|
426
505
|
for (let s = 0; s < k; s++) {
|
|
427
506
|
const series = seriesList[s];
|
|
@@ -436,6 +515,7 @@ const bars = computed<BarRect[]>(() => {
|
|
|
436
515
|
bw,
|
|
437
516
|
series.color ?? defaultColor(s),
|
|
438
517
|
series.opacity ?? 1,
|
|
518
|
+
series.blendMode,
|
|
439
519
|
raw,
|
|
440
520
|
i,
|
|
441
521
|
s,
|
|
@@ -447,6 +527,138 @@ const bars = computed<BarRect[]>(() => {
|
|
|
447
527
|
return out;
|
|
448
528
|
});
|
|
449
529
|
|
|
530
|
+
/**
|
|
531
|
+
* Per-line styling, resolved with defaults but without pixel work. Kept
|
|
532
|
+
* separate from `ResolvedSummaryLine` so the inline legend can depend on
|
|
533
|
+
* it without pulling in the chart's pixel layout (which depends on the
|
|
534
|
+
* legend row count — a circular reference otherwise).
|
|
535
|
+
*/
|
|
536
|
+
type StyledSummaryLine = {
|
|
537
|
+
data: BarChartData;
|
|
538
|
+
x?: BarChartData;
|
|
539
|
+
color: string;
|
|
540
|
+
strokeWidth: number;
|
|
541
|
+
dashed: boolean;
|
|
542
|
+
opacity: number;
|
|
543
|
+
blendMode?: BlendMode;
|
|
544
|
+
dots: boolean;
|
|
545
|
+
dotRadius: number;
|
|
546
|
+
extent: { min: number; max: number };
|
|
547
|
+
legend?: string;
|
|
548
|
+
showInLegend: boolean;
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
type ResolvedSummaryLine = StyledSummaryLine & {
|
|
552
|
+
pathD: string;
|
|
553
|
+
points: { x: number; y: number }[];
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
/** Compute the line's own value extent. */
|
|
557
|
+
function computeLineExtent(line: BarSummaryLine): { min: number; max: number } {
|
|
558
|
+
let min = Infinity;
|
|
559
|
+
let max = -Infinity;
|
|
560
|
+
for (const v of line.data) {
|
|
561
|
+
const n = Number(v);
|
|
562
|
+
if (!isFinite(n)) continue;
|
|
563
|
+
if (n < min) min = n;
|
|
564
|
+
if (n > max) max = n;
|
|
565
|
+
}
|
|
566
|
+
if (!isFinite(min)) {
|
|
567
|
+
min = 0;
|
|
568
|
+
max = 1;
|
|
569
|
+
}
|
|
570
|
+
if (line.valueMin !== undefined) {
|
|
571
|
+
min = line.valueMin;
|
|
572
|
+
} else if (min > 0) {
|
|
573
|
+
// Default to a 0 floor for all-positive data so the line shape
|
|
574
|
+
// reads as "rises from zero" rather than filling the full plot.
|
|
575
|
+
min = 0;
|
|
576
|
+
}
|
|
577
|
+
if (line.valueMax !== undefined) max = line.valueMax;
|
|
578
|
+
if (min === max) max = min + 1;
|
|
579
|
+
return { min, max };
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Project a single line point (x in category-index space, y in the
|
|
584
|
+
* line's own value space) to pixels. Orientation flips which axis is
|
|
585
|
+
* categorical vs value.
|
|
586
|
+
*/
|
|
587
|
+
function projectLinePoint(
|
|
588
|
+
x: number,
|
|
589
|
+
y: number,
|
|
590
|
+
extent: { min: number; max: number },
|
|
591
|
+
): { x: number; y: number } {
|
|
592
|
+
const base = isVertical.value ? padding.value.left : padding.value.top;
|
|
593
|
+
const categoricalPx = base + (x + 0.5) * slotSize.value;
|
|
594
|
+
const span = extent.max - extent.min || 1;
|
|
595
|
+
const frac = (y - extent.min) / span;
|
|
596
|
+
if (isVertical.value) {
|
|
597
|
+
return {
|
|
598
|
+
x: categoricalPx,
|
|
599
|
+
y: padding.value.top + innerH.value - frac * innerH.value,
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
return {
|
|
603
|
+
x: padding.value.left + frac * innerW.value,
|
|
604
|
+
y: categoricalPx,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const summaryLinesStyled = computed<StyledSummaryLine[]>(() => {
|
|
609
|
+
const lines = props.summaryLines;
|
|
610
|
+
if (!lines || lines.length === 0) return [];
|
|
611
|
+
return lines.map((line, i): StyledSummaryLine => {
|
|
612
|
+
return {
|
|
613
|
+
data: line.data ?? EMPTY_DATA,
|
|
614
|
+
x: line.x,
|
|
615
|
+
color: line.color ?? defaultLineColor(i),
|
|
616
|
+
strokeWidth: line.strokeWidth ?? 2,
|
|
617
|
+
dashed: line.dashed ?? false,
|
|
618
|
+
opacity: line.opacity ?? 1,
|
|
619
|
+
blendMode: line.blendMode,
|
|
620
|
+
dots: line.dots ?? false,
|
|
621
|
+
dotRadius: line.dotRadius ?? (line.strokeWidth ?? 2) + 1,
|
|
622
|
+
extent: computeLineExtent(line),
|
|
623
|
+
legend: line.legend,
|
|
624
|
+
showInLegend: line.showInLegend !== false,
|
|
625
|
+
};
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
const summaryLinesResolved = computed<ResolvedSummaryLine[]>(() => {
|
|
630
|
+
if (slotSize.value === 0) return [];
|
|
631
|
+
return summaryLinesStyled.value.map((line): ResolvedSummaryLine => {
|
|
632
|
+
const points: { x: number; y: number }[] = [];
|
|
633
|
+
let d = "";
|
|
634
|
+
let inSeg = false;
|
|
635
|
+
for (let j = 0; j < line.data.length; j++) {
|
|
636
|
+
const yv = Number(line.data[j]);
|
|
637
|
+
const xv = line.x ? Number(line.x[j]) : j;
|
|
638
|
+
if (!isFinite(yv) || !isFinite(xv)) {
|
|
639
|
+
inSeg = false;
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const p = projectLinePoint(xv, yv, line.extent);
|
|
643
|
+
points.push(p);
|
|
644
|
+
d += inSeg ? `L${p.x},${p.y}` : `M${p.x},${p.y}`;
|
|
645
|
+
inSeg = true;
|
|
646
|
+
}
|
|
647
|
+
return { ...line, pathD: d, points };
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
const DEFAULT_LINE_COLORS = [
|
|
652
|
+
"var(--color-fg-1, #111)",
|
|
653
|
+
"var(--color-danger, #ef4444)",
|
|
654
|
+
"var(--color-success, #10b981)",
|
|
655
|
+
"var(--color-info, #6366f1)",
|
|
656
|
+
];
|
|
657
|
+
|
|
658
|
+
function defaultLineColor(i: number): string {
|
|
659
|
+
return DEFAULT_LINE_COLORS[i % DEFAULT_LINE_COLORS.length];
|
|
660
|
+
}
|
|
661
|
+
|
|
450
662
|
const DEFAULT_COLORS = [
|
|
451
663
|
"var(--color-primary, #3b82f6)",
|
|
452
664
|
"var(--color-accent, #f59e0b)",
|
|
@@ -547,10 +759,29 @@ const categoryTickItems = computed<CategoryTickItem[]>(() => {
|
|
|
547
759
|
const out: CategoryTickItem[] = [];
|
|
548
760
|
const fmt = (label: string, i: number) =>
|
|
549
761
|
props.categoryFormat ? props.categoryFormat(label, i) : label;
|
|
762
|
+
const formatted = new Array<string>(n);
|
|
763
|
+
let maxLen = 0;
|
|
550
764
|
for (let i = 0; i < n; i++) {
|
|
765
|
+
formatted[i] = fmt(categoryLabels.value[i], i);
|
|
766
|
+
if (formatted[i].length > maxLen) maxLen = formatted[i].length;
|
|
767
|
+
}
|
|
768
|
+
// Skip labels when slots are too narrow (or short) to fit them
|
|
769
|
+
// without overlap. Vertical orientation measures horizontal label
|
|
770
|
+
// width (≈ chars × fontSize × 0.6); horizontal orientation just
|
|
771
|
+
// measures stacked line height (≈ fontSize × 1.2).
|
|
772
|
+
const fontSize = props.tickLabelStyle?.fontSize ?? TICK_LABEL_FONT_SIZE;
|
|
773
|
+
const minGap = 8;
|
|
774
|
+
const needPerLabel = isVertical.value
|
|
775
|
+
? maxLen * fontSize * 0.6 + minGap
|
|
776
|
+
: fontSize * 1.2 + minGap;
|
|
777
|
+
const stride =
|
|
778
|
+
slotSize.value > 0
|
|
779
|
+
? Math.max(1, Math.ceil(needPerLabel / slotSize.value))
|
|
780
|
+
: 1;
|
|
781
|
+
for (let i = 0; i < n; i += stride) {
|
|
551
782
|
const center = slotStart(i) + slotSize.value / 2;
|
|
552
783
|
out.push({
|
|
553
|
-
label:
|
|
784
|
+
label: formatted[i],
|
|
554
785
|
pos: center,
|
|
555
786
|
anchor: "middle",
|
|
556
787
|
});
|
|
@@ -561,13 +792,28 @@ const categoryTickItems = computed<CategoryTickItem[]>(() => {
|
|
|
561
792
|
interface InlineLegendItem {
|
|
562
793
|
label: string;
|
|
563
794
|
color: string;
|
|
795
|
+
kind: "bar" | "line";
|
|
796
|
+
dashed?: boolean;
|
|
564
797
|
}
|
|
565
798
|
|
|
566
799
|
const inlineLegendItems = computed<InlineLegendItem[]>(() => {
|
|
567
800
|
const items: InlineLegendItem[] = [];
|
|
568
801
|
allSeries.value.forEach((s, i) => {
|
|
569
802
|
if (!s.legend || s.showInLegend === false) return;
|
|
570
|
-
items.push({
|
|
803
|
+
items.push({
|
|
804
|
+
label: s.legend,
|
|
805
|
+
color: s.color ?? defaultColor(i),
|
|
806
|
+
kind: "bar",
|
|
807
|
+
});
|
|
808
|
+
});
|
|
809
|
+
summaryLinesStyled.value.forEach((line) => {
|
|
810
|
+
if (!line.legend || !line.showInLegend) return;
|
|
811
|
+
items.push({
|
|
812
|
+
label: line.legend,
|
|
813
|
+
color: line.color,
|
|
814
|
+
kind: "line",
|
|
815
|
+
dashed: line.dashed,
|
|
816
|
+
});
|
|
571
817
|
});
|
|
572
818
|
return items;
|
|
573
819
|
});
|
|
@@ -805,12 +1051,23 @@ const positionedLegendItems = computed(() => {
|
|
|
805
1051
|
<g v-if="positionedLegendItems.length > 0">
|
|
806
1052
|
<template v-for="(item, i) in positionedLegendItems" :key="'ileg' + i">
|
|
807
1053
|
<rect
|
|
1054
|
+
v-if="item.kind === 'bar'"
|
|
808
1055
|
:x="item.x"
|
|
809
1056
|
:y="item.y - 5"
|
|
810
1057
|
width="12"
|
|
811
1058
|
height="10"
|
|
812
1059
|
:fill="item.color"
|
|
813
1060
|
/>
|
|
1061
|
+
<line
|
|
1062
|
+
v-else
|
|
1063
|
+
:x1="item.x"
|
|
1064
|
+
:y1="item.y"
|
|
1065
|
+
:x2="item.x + 12"
|
|
1066
|
+
:y2="item.y"
|
|
1067
|
+
:stroke="item.color"
|
|
1068
|
+
stroke-width="2"
|
|
1069
|
+
:stroke-dasharray="item.dashed ? '4 2' : undefined"
|
|
1070
|
+
/>
|
|
814
1071
|
<text
|
|
815
1072
|
:x="item.x + 18"
|
|
816
1073
|
:y="item.y + 4"
|
|
@@ -969,7 +1226,38 @@ const positionedLegendItems = computed(() => {
|
|
|
969
1226
|
:height="bar.h"
|
|
970
1227
|
:fill="bar.color"
|
|
971
1228
|
:fill-opacity="bar.opacity"
|
|
1229
|
+
:style="bar.blendMode ? { mixBlendMode: bar.blendMode } : undefined"
|
|
972
1230
|
/>
|
|
1231
|
+
<!-- summary lines (drawn above bars, below annotations) -->
|
|
1232
|
+
<template v-for="(line, i) in summaryLinesResolved" :key="'sl' + i">
|
|
1233
|
+
<path
|
|
1234
|
+
v-if="line.pathD"
|
|
1235
|
+
data-testid="summary-line"
|
|
1236
|
+
:d="line.pathD"
|
|
1237
|
+
fill="none"
|
|
1238
|
+
:stroke="line.color"
|
|
1239
|
+
:stroke-width="line.strokeWidth"
|
|
1240
|
+
:stroke-opacity="line.opacity"
|
|
1241
|
+
:stroke-dasharray="line.dashed ? '6 3' : undefined"
|
|
1242
|
+
stroke-linecap="round"
|
|
1243
|
+
stroke-linejoin="round"
|
|
1244
|
+
:style="line.blendMode ? { mixBlendMode: line.blendMode } : undefined"
|
|
1245
|
+
/>
|
|
1246
|
+
<template v-if="line.dots">
|
|
1247
|
+
<circle
|
|
1248
|
+
v-for="(pt, j) in line.points"
|
|
1249
|
+
:key="'sld' + i + '-' + j"
|
|
1250
|
+
:cx="pt.x"
|
|
1251
|
+
:cy="pt.y"
|
|
1252
|
+
:r="line.dotRadius"
|
|
1253
|
+
:fill="line.color"
|
|
1254
|
+
:fill-opacity="line.opacity"
|
|
1255
|
+
:style="
|
|
1256
|
+
line.blendMode ? { mixBlendMode: line.blendMode } : undefined
|
|
1257
|
+
"
|
|
1258
|
+
/>
|
|
1259
|
+
</template>
|
|
1260
|
+
</template>
|
|
973
1261
|
<!-- Tooltip: interaction overlay -->
|
|
974
1262
|
<rect
|
|
975
1263
|
v-if="hasTooltipSlot"
|
|
@@ -478,6 +478,66 @@ busy background — the outline keeps each series visually distinct.
|
|
|
478
478
|
</template>
|
|
479
479
|
</ComponentDemo>
|
|
480
480
|
|
|
481
|
+
### Blend mode
|
|
482
|
+
|
|
483
|
+
Set `blendMode` on a `Series` to apply a CSS `mix-blend-mode` to that
|
|
484
|
+
series' line (and dots, if enabled). Useful when two thick or
|
|
485
|
+
high-contrast lines overlap — `"multiply"` darkens the crossing point
|
|
486
|
+
on light backgrounds, `"screen"` lightens on dark, `"difference"`
|
|
487
|
+
inverts. The white `outline`, if any, is unaffected.
|
|
488
|
+
|
|
489
|
+
<ComponentDemo>
|
|
490
|
+
<LineChart
|
|
491
|
+
:series="[
|
|
492
|
+
{
|
|
493
|
+
data: Array.from({ length: 30 }, (_, t) => 20 + 15 * Math.sin(t / 4)),
|
|
494
|
+
color: '#ef4444',
|
|
495
|
+
strokeWidth: 3,
|
|
496
|
+
blendMode: 'multiply',
|
|
497
|
+
legend: 'Strain A',
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
data: Array.from({ length: 30 }, (_, t) => 20 + 15 * Math.cos(t / 4)),
|
|
501
|
+
color: '#3b82f6',
|
|
502
|
+
strokeWidth: 3,
|
|
503
|
+
blendMode: 'multiply',
|
|
504
|
+
legend: 'Strain B',
|
|
505
|
+
},
|
|
506
|
+
]"
|
|
507
|
+
:height="240"
|
|
508
|
+
x-label="Day"
|
|
509
|
+
y-label="Incidence"
|
|
510
|
+
/>
|
|
511
|
+
|
|
512
|
+
<template #code>
|
|
513
|
+
|
|
514
|
+
```vue
|
|
515
|
+
<LineChart
|
|
516
|
+
:series="[
|
|
517
|
+
{
|
|
518
|
+
data: strainA,
|
|
519
|
+
color: '#ef4444',
|
|
520
|
+
strokeWidth: 3,
|
|
521
|
+
blendMode: 'multiply',
|
|
522
|
+
legend: 'Strain A',
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
data: strainB,
|
|
526
|
+
color: '#3b82f6',
|
|
527
|
+
strokeWidth: 3,
|
|
528
|
+
blendMode: 'multiply',
|
|
529
|
+
legend: 'Strain B',
|
|
530
|
+
},
|
|
531
|
+
]"
|
|
532
|
+
:height="240"
|
|
533
|
+
x-label="Day"
|
|
534
|
+
y-label="Incidence"
|
|
535
|
+
/>
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
</template>
|
|
539
|
+
</ComponentDemo>
|
|
540
|
+
|
|
481
541
|
### Grid lines
|
|
482
542
|
|
|
483
543
|
<ComponentDemo>
|
|
@@ -35,6 +35,8 @@ import {
|
|
|
35
35
|
type ChartHoverPayload,
|
|
36
36
|
type ChartTooltipBaseProps,
|
|
37
37
|
type ChartTooltipValue,
|
|
38
|
+
type BlendMode,
|
|
39
|
+
type LineMarkStyle,
|
|
38
40
|
} from "../_shared/index.js";
|
|
39
41
|
|
|
40
42
|
/**
|
|
@@ -52,7 +54,13 @@ export type LineChartData = ChartData;
|
|
|
52
54
|
*/
|
|
53
55
|
export type LineChartXInput = LineChartData | readonly (string | Date)[];
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
/**
|
|
58
|
+
* A single line/dot series. Inherits visual styling
|
|
59
|
+
* (`color`, `strokeWidth`, `dashed`, `opacity`, `blendMode`, `dots`,
|
|
60
|
+
* `dotRadius`, `legend`, `showInLegend`) from `LineMarkStyle`, shared
|
|
61
|
+
* with `BarChart`'s `BarSummaryLine`.
|
|
62
|
+
*/
|
|
63
|
+
export interface Series extends LineMarkStyle {
|
|
56
64
|
/**
|
|
57
65
|
* Y-values. One of `y` or `data` must be supplied; `y` wins if both
|
|
58
66
|
* are set.
|
|
@@ -67,12 +75,11 @@ export interface Series {
|
|
|
67
75
|
* date strings or `Date` objects to enable date-axis mode.
|
|
68
76
|
*/
|
|
69
77
|
x?: LineChartXInput;
|
|
70
|
-
|
|
71
|
-
dashed?: boolean;
|
|
72
|
-
strokeWidth?: number;
|
|
73
|
-
opacity?: number;
|
|
78
|
+
/** Overrides `opacity` for the line stroke only. */
|
|
74
79
|
lineOpacity?: number;
|
|
80
|
+
/** Overrides `opacity` for the dots only. */
|
|
75
81
|
dotOpacity?: number;
|
|
82
|
+
/** Draw the line. Default true. */
|
|
76
83
|
line?: boolean;
|
|
77
84
|
/**
|
|
78
85
|
* Draw a page-colored stroke behind the line so it stays visually
|
|
@@ -80,17 +87,8 @@ export interface Series {
|
|
|
80
87
|
* effect when `line` is `false`.
|
|
81
88
|
*/
|
|
82
89
|
outline?: boolean;
|
|
83
|
-
dots?: boolean;
|
|
84
|
-
dotRadius?: number;
|
|
85
90
|
dotFill?: string;
|
|
86
91
|
dotStroke?: string;
|
|
87
|
-
/** Label shown in the inline legend */
|
|
88
|
-
legend?: string;
|
|
89
|
-
/**
|
|
90
|
-
* Whether this series appears in the inline legend. Defaults to true.
|
|
91
|
-
* Has no effect when `legend` is unset (no legend entry to begin with).
|
|
92
|
-
*/
|
|
93
|
-
showInLegend?: boolean;
|
|
94
92
|
/**
|
|
95
93
|
* Whether this series contributes a value to the tooltip and shows a
|
|
96
94
|
* hover dot. Defaults to true. The series line/dots are still drawn.
|
|
@@ -1208,6 +1206,7 @@ const positionedLegendItems = computed(() => {
|
|
|
1208
1206
|
:stroke-width="s.strokeWidth ?? 1.5"
|
|
1209
1207
|
:stroke-opacity="s.lineOpacity ?? s.opacity ?? lineOpacity"
|
|
1210
1208
|
:stroke-dasharray="s.dashed ? '6 3' : undefined"
|
|
1209
|
+
:style="s.blendMode ? { mixBlendMode: s.blendMode } : undefined"
|
|
1211
1210
|
/>
|
|
1212
1211
|
<template v-if="s.dots">
|
|
1213
1212
|
<circle
|
|
@@ -1219,6 +1218,7 @@ const positionedLegendItems = computed(() => {
|
|
|
1219
1218
|
:fill="s.dotFill ?? s.color ?? 'currentColor'"
|
|
1220
1219
|
:fill-opacity="s.dotOpacity ?? s.opacity ?? lineOpacity"
|
|
1221
1220
|
:stroke="s.dotStroke ?? 'none'"
|
|
1221
|
+
:style="s.blendMode ? { mixBlendMode: s.blendMode } : undefined"
|
|
1222
1222
|
/>
|
|
1223
1223
|
</template>
|
|
1224
1224
|
</template>
|
|
@@ -35,6 +35,59 @@ export interface LabelStyle {
|
|
|
35
35
|
fontWeight?: number | string;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* CSS `mix-blend-mode` values usable on chart series. Drives how a
|
|
40
|
+
* series' pixels combine with what's painted behind them. Useful for
|
|
41
|
+
* overlapping series (e.g. `"multiply"` darkens light fills where bars
|
|
42
|
+
* overlap; `"screen"` lightens dark fills).
|
|
43
|
+
*/
|
|
44
|
+
export type BlendMode =
|
|
45
|
+
| "normal"
|
|
46
|
+
| "multiply"
|
|
47
|
+
| "screen"
|
|
48
|
+
| "overlay"
|
|
49
|
+
| "darken"
|
|
50
|
+
| "lighten"
|
|
51
|
+
| "color-dodge"
|
|
52
|
+
| "color-burn"
|
|
53
|
+
| "hard-light"
|
|
54
|
+
| "soft-light"
|
|
55
|
+
| "difference"
|
|
56
|
+
| "exclusion"
|
|
57
|
+
| "hue"
|
|
58
|
+
| "saturation"
|
|
59
|
+
| "color"
|
|
60
|
+
| "luminosity";
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Visual styling shared by anything drawn as "a stroked line with
|
|
64
|
+
* optional dots" — used by `LineChart`'s `Series` and `BarChart`'s
|
|
65
|
+
* `BarSummaryLine`. Chart-specific data (y/x arrays, scaling overrides,
|
|
66
|
+
* tooltip/outline behavior) lives on the extending type.
|
|
67
|
+
*/
|
|
68
|
+
export interface LineMarkStyle {
|
|
69
|
+
color?: string;
|
|
70
|
+
strokeWidth?: number;
|
|
71
|
+
dashed?: boolean;
|
|
72
|
+
opacity?: number;
|
|
73
|
+
/**
|
|
74
|
+
* CSS `mix-blend-mode` applied to the line and dots. Lets overlapping
|
|
75
|
+
* marks combine their colors instead of one obscuring the other.
|
|
76
|
+
*/
|
|
77
|
+
blendMode?: BlendMode;
|
|
78
|
+
/** Draw a dot at each sample point. Default false. */
|
|
79
|
+
dots?: boolean;
|
|
80
|
+
/** Radius of each dot in pixels. */
|
|
81
|
+
dotRadius?: number;
|
|
82
|
+
/** Label shown in the inline legend. */
|
|
83
|
+
legend?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Whether this mark appears in the inline legend. Defaults to true.
|
|
86
|
+
* Has no effect when `legend` is unset.
|
|
87
|
+
*/
|
|
88
|
+
showInLegend?: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
38
91
|
/**
|
|
39
92
|
* Props common to every cartesian chart component. Anything specific to
|
|
40
93
|
* the chart type (series shape, layout, value-axis details) lives on the
|
package/charts/_shared/index.ts
CHANGED
package/charts/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export {
|
|
|
11
11
|
default as BarChart,
|
|
12
12
|
type BarChartData,
|
|
13
13
|
type BarSeries,
|
|
14
|
+
type BarSummaryLine,
|
|
14
15
|
} from "./BarChart/BarChart.vue";
|
|
15
16
|
export {
|
|
16
17
|
default as ChoroplethMap,
|
|
@@ -33,3 +34,4 @@ export {
|
|
|
33
34
|
type ColumnWidth,
|
|
34
35
|
} from "./DataTable/DataTable.vue";
|
|
35
36
|
export type { ChartAnnotation } from "./_shared/annotations.js";
|
|
37
|
+
export type { BlendMode, LineMarkStyle } from "./_shared/chartProps.js";
|
package/index.json
CHANGED