@sentropic/design-system-svelte 0.34.24 → 0.34.26
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/AreaChart.svelte +61 -4
- package/dist/AreaChart.svelte.d.ts +14 -0
- package/dist/AreaChart.svelte.d.ts.map +1 -1
- package/dist/BarChart.svelte +59 -4
- package/dist/BarChart.svelte.d.ts +14 -0
- package/dist/BarChart.svelte.d.ts.map +1 -1
- package/dist/ComboChart.svelte +89 -32
- package/dist/ComboChart.svelte.d.ts +9 -0
- package/dist/ComboChart.svelte.d.ts.map +1 -1
- package/dist/LineChart.svelte +60 -4
- package/dist/LineChart.svelte.d.ts +14 -0
- package/dist/LineChart.svelte.d.ts.map +1 -1
- package/dist/StackedBarChart.svelte +62 -16
- package/dist/StackedBarChart.svelte.d.ts +10 -0
- package/dist/StackedBarChart.svelte.d.ts.map +1 -1
- package/dist/chartCrosshair.d.ts +19 -0
- package/dist/chartCrosshair.d.ts.map +1 -0
- package/dist/chartCrosshair.js +51 -0
- package/package.json +1 -1
package/dist/AreaChart.svelte
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
type ChartAnnotation
|
|
25
25
|
} from "./chartAnnotations.js";
|
|
26
26
|
import { formatDataLabel, normalizeDataLabels, type DataLabelsProp } from "./chartDataLabels.js";
|
|
27
|
+
import { keyForX, resolveActiveIndex } from "./chartCrosshair.js";
|
|
27
28
|
|
|
28
29
|
type AreaChartProps = {
|
|
29
30
|
data: (number | AreaChartDatum)[];
|
|
@@ -46,6 +47,20 @@
|
|
|
46
47
|
* ChartDataList.
|
|
47
48
|
*/
|
|
48
49
|
dataLabels?: DataLabelsProp;
|
|
50
|
+
/**
|
|
51
|
+
* CONTROLLED synchronised hover key (FR-3). A datum's key is `String(x)`. When
|
|
52
|
+
* provided (string or null), the crosshair + tooltip track this key instead of
|
|
53
|
+
* the chart's internal pointer hover (null ⇒ nothing shown), letting a parent
|
|
54
|
+
* share one hover channel across several aligned charts. Absent (`undefined`)
|
|
55
|
+
* keeps the legacy uncontrolled behaviour.
|
|
56
|
+
*/
|
|
57
|
+
hoverKey?: string | null;
|
|
58
|
+
/**
|
|
59
|
+
* Emitted when the user hovers a datum (its key) or leaves the plot (`null`).
|
|
60
|
+
* Always fired on pointer move/leave — even while CONTROLLED — so dataviz can
|
|
61
|
+
* keep the shared hover channel in sync.
|
|
62
|
+
*/
|
|
63
|
+
onHoverKeyChange?: (key: string | null) => void;
|
|
49
64
|
class?: string;
|
|
50
65
|
};
|
|
51
66
|
|
|
@@ -58,6 +73,8 @@
|
|
|
58
73
|
label,
|
|
59
74
|
annotations,
|
|
60
75
|
dataLabels,
|
|
76
|
+
hoverKey,
|
|
77
|
+
onHoverKeyChange,
|
|
61
78
|
class: className
|
|
62
79
|
}: AreaChartProps = $props();
|
|
63
80
|
|
|
@@ -271,19 +288,34 @@
|
|
|
271
288
|
return entries;
|
|
272
289
|
});
|
|
273
290
|
|
|
291
|
+
// Stable key per datum (FR-3): `String(x)` of the normalised datum (a bare
|
|
292
|
+
// number becomes its index). Resolves a controlled `hoverKey` to an index and
|
|
293
|
+
// feeds `onHoverKeyChange` from pointer events.
|
|
294
|
+
const hoverKeys = $derived(normalizedData.map((d) => keyForX(d.x)));
|
|
295
|
+
function emitHoverKey(index: number | null) {
|
|
296
|
+
onHoverKeyChange?.(index == null ? null : hoverKeys[index] ?? null);
|
|
297
|
+
}
|
|
274
298
|
function handleLeave() {
|
|
275
299
|
hoveredIndex = null;
|
|
300
|
+
emitHoverKey(null);
|
|
276
301
|
}
|
|
277
302
|
function handleVisualPointerMove(event: PointerEvent) {
|
|
278
303
|
const target = event.target;
|
|
279
304
|
if (!(target instanceof Element)) {
|
|
280
305
|
hoveredIndex = null;
|
|
306
|
+
emitHoverKey(null);
|
|
281
307
|
return;
|
|
282
308
|
}
|
|
283
|
-
const
|
|
284
|
-
|
|
309
|
+
const raw = Number(target.getAttribute("data-chart-index"));
|
|
310
|
+
const index = Number.isInteger(raw) ? raw : null;
|
|
311
|
+
hoveredIndex = index;
|
|
312
|
+
emitHoverKey(index);
|
|
285
313
|
}
|
|
286
314
|
|
|
315
|
+
// Index whose crosshair/tooltip is DISPLAYED: the controlled `hoverKey` when
|
|
316
|
+
// provided (resolved against `hoverKeys`), else the internal pointer index.
|
|
317
|
+
const activeIndex = $derived(resolveActiveIndex(hoverKey, hoveredIndex, hoverKeys));
|
|
318
|
+
|
|
287
319
|
// Generates a unique gradient id to avoid conflicts when rendering multiple charts on the same page
|
|
288
320
|
const gradientId = $derived.by(() => {
|
|
289
321
|
return `st-areachart-gradient-${Math.random().toString(36).substring(2, 9)}`;
|
|
@@ -441,13 +473,23 @@
|
|
|
441
473
|
{/each}
|
|
442
474
|
</g>
|
|
443
475
|
{/if}
|
|
476
|
+
|
|
477
|
+
<!-- Crosshair (FR-3) — a tokenised vertical line + marker at the active key.
|
|
478
|
+
Decorative (aria-hidden); the value is in the tooltip + ChartDataList. -->
|
|
479
|
+
{#if activeIndex >= 0 && points[activeIndex]}
|
|
480
|
+
{@const cp = points[activeIndex]}
|
|
481
|
+
<g class="st-areaChart__crosshair" aria-hidden="true">
|
|
482
|
+
<line class="st-areaChart__crosshairLine" x1={cp.x} x2={cp.x} y1={MARGIN.top} y2={MARGIN.top + plotHeight} />
|
|
483
|
+
<circle class="st-areaChart__crosshairMarker" cx={cp.x} cy={cp.y} r="5" />
|
|
484
|
+
</g>
|
|
485
|
+
{/if}
|
|
444
486
|
</svg>
|
|
445
487
|
</div>
|
|
446
488
|
|
|
447
489
|
<ChartDataList {label} items={dataValueItems} />
|
|
448
490
|
|
|
449
|
-
{#if
|
|
450
|
-
{@const p = points[
|
|
491
|
+
{#if activeIndex >= 0 && points[activeIndex]}
|
|
492
|
+
{@const p = points[activeIndex]}
|
|
451
493
|
<div
|
|
452
494
|
class="st-areaChart__tooltip"
|
|
453
495
|
role="presentation"
|
|
@@ -588,4 +630,19 @@
|
|
|
588
630
|
font-size: 0.6875rem;
|
|
589
631
|
font-weight: 600;
|
|
590
632
|
}
|
|
633
|
+
|
|
634
|
+
/* --- Crosshair layer (FR-3) ----------------------------------------------
|
|
635
|
+
A tokenised dashed vertical line at the active (hovered/controlled) key,
|
|
636
|
+
plus an emphasised marker on the point. Decorative (aria-hidden). */
|
|
637
|
+
.st-areaChart__crosshairLine {
|
|
638
|
+
stroke: var(--st-semantic-border-strong);
|
|
639
|
+
stroke-width: 1;
|
|
640
|
+
stroke-dasharray: 3 3;
|
|
641
|
+
opacity: 0.7;
|
|
642
|
+
}
|
|
643
|
+
.st-areaChart__crosshairMarker {
|
|
644
|
+
fill: currentColor;
|
|
645
|
+
stroke: var(--st-semantic-surface-default);
|
|
646
|
+
stroke-width: 2;
|
|
647
|
+
}
|
|
591
648
|
</style>
|
|
@@ -26,6 +26,20 @@ type AreaChartProps = {
|
|
|
26
26
|
* ChartDataList.
|
|
27
27
|
*/
|
|
28
28
|
dataLabels?: DataLabelsProp;
|
|
29
|
+
/**
|
|
30
|
+
* CONTROLLED synchronised hover key (FR-3). A datum's key is `String(x)`. When
|
|
31
|
+
* provided (string or null), the crosshair + tooltip track this key instead of
|
|
32
|
+
* the chart's internal pointer hover (null ⇒ nothing shown), letting a parent
|
|
33
|
+
* share one hover channel across several aligned charts. Absent (`undefined`)
|
|
34
|
+
* keeps the legacy uncontrolled behaviour.
|
|
35
|
+
*/
|
|
36
|
+
hoverKey?: string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Emitted when the user hovers a datum (its key) or leaves the plot (`null`).
|
|
39
|
+
* Always fired on pointer move/leave — even while CONTROLLED — so dataviz can
|
|
40
|
+
* keep the shared hover channel in sync.
|
|
41
|
+
*/
|
|
42
|
+
onHoverKeyChange?: (key: string | null) => void;
|
|
29
43
|
class?: string;
|
|
30
44
|
};
|
|
31
45
|
declare const AreaChart: import("svelte").Component<AreaChartProps, {}, "">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AreaChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/AreaChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,aAAa,GACrB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,cAAc,GAAG;IAC3B,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,CAAC;CACX,CAAC;AAIJ,OAAO,EAIH,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AACjC,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"AreaChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/AreaChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,aAAa,GACrB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,cAAc,GAAG;IAC3B,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,CAAC;CACX,CAAC;AAIJ,OAAO,EAIH,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AACjC,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI/F,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,CAAC,MAAM,GAAG,cAAc,CAAC,EAAE,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAkYJ,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
package/dist/BarChart.svelte
CHANGED
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
type ChartAnnotation
|
|
58
58
|
} from "./chartAnnotations.js";
|
|
59
59
|
import { formatDataLabel, normalizeDataLabels, type DataLabelsProp } from "./chartDataLabels.js";
|
|
60
|
+
import { resolveActiveIndex } from "./chartCrosshair.js";
|
|
60
61
|
|
|
61
62
|
type BarChartProps = {
|
|
62
63
|
data: BarChartDatum[];
|
|
@@ -124,6 +125,20 @@
|
|
|
124
125
|
* cross-chart parity and otherwise ignored.
|
|
125
126
|
*/
|
|
126
127
|
showLegend?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* CONTROLLED synchronised hover key (FR-3). A bar's key is its `label`. When
|
|
130
|
+
* provided (string or null), the crosshair + tooltip track this key instead of
|
|
131
|
+
* the chart's internal pointer hover (null ⇒ nothing shown), letting a parent
|
|
132
|
+
* share one hover channel across several aligned charts. Absent (`undefined`)
|
|
133
|
+
* keeps the legacy uncontrolled behaviour. Independent of `selectedKeys`.
|
|
134
|
+
*/
|
|
135
|
+
hoverKey?: string | null;
|
|
136
|
+
/**
|
|
137
|
+
* Emitted when the user hovers a bar (its `label`) or leaves the plot (`null`).
|
|
138
|
+
* Always fired on pointer move/leave — even while CONTROLLED — so dataviz can
|
|
139
|
+
* keep the shared hover channel in sync.
|
|
140
|
+
*/
|
|
141
|
+
onHoverKeyChange?: (key: string | null) => void;
|
|
127
142
|
class?: string;
|
|
128
143
|
};
|
|
129
144
|
|
|
@@ -144,6 +159,8 @@
|
|
|
144
159
|
scale = "linear",
|
|
145
160
|
invertAxis = false,
|
|
146
161
|
showLegend,
|
|
162
|
+
hoverKey,
|
|
163
|
+
onHoverKeyChange,
|
|
147
164
|
class: className
|
|
148
165
|
}: BarChartProps = $props();
|
|
149
166
|
|
|
@@ -599,19 +616,33 @@
|
|
|
599
616
|
}));
|
|
600
617
|
});
|
|
601
618
|
|
|
619
|
+
// Stable key per bar (FR-3): its `label`. Resolves a controlled `hoverKey` to
|
|
620
|
+
// an index and feeds `onHoverKeyChange` from pointer events.
|
|
621
|
+
const hoverKeys = $derived(bars.map((b) => b.datum.label));
|
|
622
|
+
function emitHoverKey(index: number | null) {
|
|
623
|
+
onHoverKeyChange?.(index == null ? null : hoverKeys[index] ?? null);
|
|
624
|
+
}
|
|
602
625
|
function handleLeave() {
|
|
603
626
|
hoveredIndex = null;
|
|
627
|
+
emitHoverKey(null);
|
|
604
628
|
}
|
|
605
629
|
function handleVisualPointerMove(event: PointerEvent) {
|
|
606
630
|
const target = event.target;
|
|
607
631
|
if (!(target instanceof Element)) {
|
|
608
632
|
hoveredIndex = null;
|
|
633
|
+
emitHoverKey(null);
|
|
609
634
|
return;
|
|
610
635
|
}
|
|
611
|
-
const
|
|
612
|
-
|
|
636
|
+
const raw = Number(target.getAttribute("data-chart-index"));
|
|
637
|
+
const index = Number.isInteger(raw) ? raw : null;
|
|
638
|
+
hoveredIndex = index;
|
|
639
|
+
emitHoverKey(index);
|
|
613
640
|
}
|
|
614
641
|
|
|
642
|
+
// Index whose crosshair/tooltip is DISPLAYED: the controlled `hoverKey` when
|
|
643
|
+
// provided (resolved against `hoverKeys`), else the internal pointer index.
|
|
644
|
+
const activeIndex = $derived(resolveActiveIndex(hoverKey, hoveredIndex, hoverKeys));
|
|
645
|
+
|
|
615
646
|
const classes = () => ["st-barChart", className].filter(Boolean).join(" ");
|
|
616
647
|
</script>
|
|
617
648
|
|
|
@@ -836,6 +867,20 @@
|
|
|
836
867
|
{/each}
|
|
837
868
|
</g>
|
|
838
869
|
{/if}
|
|
870
|
+
|
|
871
|
+
<!-- Crosshair (FR-3) — a tokenised dashed line on the CATEGORY axis at the
|
|
872
|
+
active bar: vertical (vertical bars) / horizontal (horizontal bars).
|
|
873
|
+
Decorative (aria-hidden); the value is in the tooltip + ChartDataList. -->
|
|
874
|
+
{#if activeIndex >= 0 && bars[activeIndex]}
|
|
875
|
+
{@const cb = bars[activeIndex]}
|
|
876
|
+
<g class="st-barChart__crosshair" aria-hidden="true">
|
|
877
|
+
{#if isVertical}
|
|
878
|
+
<line class="st-barChart__crosshairLine" x1={cb.cx} x2={cb.cx} y1={MARGIN.top} y2={MARGIN.top + scales.plotHeight} />
|
|
879
|
+
{:else}
|
|
880
|
+
<line class="st-barChart__crosshairLine" x1={MARGIN.left} x2={MARGIN.left + scales.plotWidth} y1={cb.cy} y2={cb.cy} />
|
|
881
|
+
{/if}
|
|
882
|
+
</g>
|
|
883
|
+
{/if}
|
|
839
884
|
</svg>
|
|
840
885
|
</div>
|
|
841
886
|
|
|
@@ -861,8 +906,8 @@
|
|
|
861
906
|
|
|
862
907
|
<ChartDataList {label} items={dataValueItems} />
|
|
863
908
|
|
|
864
|
-
{#if
|
|
865
|
-
{@const bar = bars[
|
|
909
|
+
{#if activeIndex >= 0 && bars[activeIndex]}
|
|
910
|
+
{@const bar = bars[activeIndex]}
|
|
866
911
|
<div
|
|
867
912
|
class="st-barChart__tooltip"
|
|
868
913
|
role="presentation"
|
|
@@ -1124,4 +1169,14 @@
|
|
|
1124
1169
|
font-size: 0.6875rem;
|
|
1125
1170
|
font-weight: 600;
|
|
1126
1171
|
}
|
|
1172
|
+
|
|
1173
|
+
/* --- Crosshair layer (FR-3) ----------------------------------------------
|
|
1174
|
+
A tokenised dashed line on the CATEGORY axis at the active (hovered/
|
|
1175
|
+
controlled) bar. Decorative (aria-hidden). */
|
|
1176
|
+
.st-barChart__crosshairLine {
|
|
1177
|
+
stroke: var(--st-semantic-border-strong);
|
|
1178
|
+
stroke-width: 1;
|
|
1179
|
+
stroke-dasharray: 3 3;
|
|
1180
|
+
opacity: 0.7;
|
|
1181
|
+
}
|
|
1127
1182
|
</style>
|
|
@@ -99,6 +99,20 @@ type BarChartProps = {
|
|
|
99
99
|
* cross-chart parity and otherwise ignored.
|
|
100
100
|
*/
|
|
101
101
|
showLegend?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* CONTROLLED synchronised hover key (FR-3). A bar's key is its `label`. When
|
|
104
|
+
* provided (string or null), the crosshair + tooltip track this key instead of
|
|
105
|
+
* the chart's internal pointer hover (null ⇒ nothing shown), letting a parent
|
|
106
|
+
* share one hover channel across several aligned charts. Absent (`undefined`)
|
|
107
|
+
* keeps the legacy uncontrolled behaviour. Independent of `selectedKeys`.
|
|
108
|
+
*/
|
|
109
|
+
hoverKey?: string | null;
|
|
110
|
+
/**
|
|
111
|
+
* Emitted when the user hovers a bar (its `label`) or leaves the plot (`null`).
|
|
112
|
+
* Always fired on pointer move/leave — even while CONTROLLED — so dataviz can
|
|
113
|
+
* keep the shared hover channel in sync.
|
|
114
|
+
*/
|
|
115
|
+
onHoverKeyChange?: (key: string | null) => void;
|
|
102
116
|
class?: string;
|
|
103
117
|
};
|
|
104
118
|
declare const BarChart: import("svelte").Component<BarChartProps, {}, "">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BarChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/BarChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,IAAI,CAAC,EAAE,GAAG,GAAG,GAAG,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,KAAK,CAAC;AAI5C,OAAO,EAIH,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AACjC,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"BarChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/BarChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,IAAI,CAAC,EAAE,GAAG,GAAG,GAAG,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,KAAK,CAAC;AAI5C,OAAO,EAIH,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AACjC,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI/F,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,+DAA+D;IAC/D,cAAc,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACtC,oDAAoD;IACpD,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;IACpB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA8qBJ,QAAA,MAAM,QAAQ,mDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
package/dist/ComboChart.svelte
CHANGED
|
@@ -33,6 +33,15 @@
|
|
|
33
33
|
leftAxisLabel?: string;
|
|
34
34
|
rightAxisLabel?: string;
|
|
35
35
|
legend?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Interactive legend (FR-4). Ids/labels of bar/line series hidden from the
|
|
38
|
+
* render (controlled by the parent; default = all visible). Hidden series
|
|
39
|
+
* are omitted and their legend item is shown "off" (`aria-pressed`).
|
|
40
|
+
* Undefined → legacy non-interactive legend, unless `onToggleSeries` is set.
|
|
41
|
+
*/
|
|
42
|
+
hiddenSeries?: string[];
|
|
43
|
+
/** Emitted on click / Enter / Space on a legend item. */
|
|
44
|
+
onToggleSeries?: (seriesId: string) => void;
|
|
36
45
|
width?: number;
|
|
37
46
|
height?: number;
|
|
38
47
|
label: string;
|
|
@@ -46,12 +55,18 @@
|
|
|
46
55
|
leftAxisLabel,
|
|
47
56
|
rightAxisLabel,
|
|
48
57
|
legend = true,
|
|
58
|
+
hiddenSeries,
|
|
59
|
+
onToggleSeries,
|
|
49
60
|
width = 480,
|
|
50
61
|
height = 240,
|
|
51
62
|
label,
|
|
52
63
|
class: className
|
|
53
64
|
}: ComboChartProps = $props();
|
|
54
65
|
|
|
66
|
+
// Interactive legend is active as soon as the parent wires either prop.
|
|
67
|
+
const legendInteractive = $derived(onToggleSeries !== undefined || hiddenSeries !== undefined);
|
|
68
|
+
const hiddenSet = $derived(new Set(hiddenSeries ?? []));
|
|
69
|
+
|
|
55
70
|
const MARGIN = { top: 12, right: 52, bottom: 32, left: 52 };
|
|
56
71
|
|
|
57
72
|
function niceTicks(min: number, max: number, target = 5): number[] {
|
|
@@ -92,8 +107,9 @@
|
|
|
92
107
|
const plotHeight = $derived(Math.max(height - MARGIN.top - MARGIN.bottom, 1));
|
|
93
108
|
|
|
94
109
|
// Left axis (bars): include zero in the domain so bars rest on a baseline.
|
|
110
|
+
// Hidden series are excluded so the axis rescales to what is visible.
|
|
95
111
|
const leftScale = $derived.by(() => {
|
|
96
|
-
const values = bars.flatMap((s) => s.data);
|
|
112
|
+
const values = bars.filter((s) => !hiddenSet.has(s.label)).flatMap((s) => s.data);
|
|
97
113
|
const minRaw = Math.min(0, ...(values.length ? values : [0]));
|
|
98
114
|
const maxRaw = Math.max(0, ...(values.length ? values : [0]));
|
|
99
115
|
const ticks = niceTicks(minRaw, maxRaw, 5);
|
|
@@ -102,7 +118,7 @@
|
|
|
102
118
|
|
|
103
119
|
// Right axis (lines): padded domain like LineChart.
|
|
104
120
|
const rightScale = $derived.by(() => {
|
|
105
|
-
const values = lines.flatMap((s) => s.data);
|
|
121
|
+
const values = lines.filter((s) => !hiddenSet.has(s.label)).flatMap((s) => s.data);
|
|
106
122
|
if (values.length === 0) {
|
|
107
123
|
const ticks = niceTicks(0, 1, 5);
|
|
108
124
|
return { ticks, domainMin: ticks[0], domainMax: ticks[ticks.length - 1] };
|
|
@@ -136,6 +152,7 @@
|
|
|
136
152
|
const groupX = MARGIN.left + band * ci + (band - groupWidth) / 2;
|
|
137
153
|
const segments = bars
|
|
138
154
|
.map((series, si) => {
|
|
155
|
+
if (hiddenSet.has(series.label)) return null;
|
|
139
156
|
const raw = series.data[ci];
|
|
140
157
|
if (!isPresent(raw)) return null;
|
|
141
158
|
const value = raw;
|
|
@@ -203,6 +220,7 @@
|
|
|
203
220
|
path,
|
|
204
221
|
points,
|
|
205
222
|
seriesLabel: series.label,
|
|
223
|
+
hidden: hiddenSet.has(series.label),
|
|
206
224
|
tone: series.tone ?? `category${((bars.length + li) % 8) + 1}`
|
|
207
225
|
};
|
|
208
226
|
});
|
|
@@ -240,12 +258,12 @@
|
|
|
240
258
|
]);
|
|
241
259
|
|
|
242
260
|
const dataValueItems = $derived([
|
|
243
|
-
...bars
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
...lines
|
|
247
|
-
|
|
248
|
-
|
|
261
|
+
...bars
|
|
262
|
+
.filter((s) => !hiddenSet.has(s.label))
|
|
263
|
+
.flatMap((s) => categories.map((c, ci) => `${s.label}, ${c}: ${s.data[ci] ?? 0}`)),
|
|
264
|
+
...lines
|
|
265
|
+
.filter((s) => !hiddenSet.has(s.label))
|
|
266
|
+
.flatMap((s) => categories.map((c, ci) => `${s.label}, ${c}: ${s.data[ci] ?? 0}`))
|
|
249
267
|
]);
|
|
250
268
|
|
|
251
269
|
type Hover =
|
|
@@ -404,25 +422,27 @@
|
|
|
404
422
|
|
|
405
423
|
<!-- lines -->
|
|
406
424
|
{#each lineSeries as series, li (li)}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
{#each series.points as p, pi (pi)}
|
|
416
|
-
<circle
|
|
417
|
-
class="st-comboChart__dot st-comboChart__dot--{series.tone}"
|
|
418
|
-
cx={p.x}
|
|
419
|
-
cy={p.y}
|
|
420
|
-
r="4"
|
|
421
|
-
data-chart-kind="line"
|
|
422
|
-
data-chart-a={li}
|
|
423
|
-
data-chart-b={pi}
|
|
425
|
+
{#if !series.hidden}
|
|
426
|
+
<path
|
|
427
|
+
class="st-comboChart__line st-comboChart__line--{series.tone}"
|
|
428
|
+
d={series.path}
|
|
429
|
+
fill="none"
|
|
430
|
+
stroke-width="2"
|
|
431
|
+
stroke-linecap="round"
|
|
432
|
+
stroke-linejoin="round"
|
|
424
433
|
/>
|
|
425
|
-
|
|
434
|
+
{#each series.points as p, pi (pi)}
|
|
435
|
+
<circle
|
|
436
|
+
class="st-comboChart__dot st-comboChart__dot--{series.tone}"
|
|
437
|
+
cx={p.x}
|
|
438
|
+
cy={p.y}
|
|
439
|
+
r="4"
|
|
440
|
+
data-chart-kind="line"
|
|
441
|
+
data-chart-a={li}
|
|
442
|
+
data-chart-b={pi}
|
|
443
|
+
/>
|
|
444
|
+
{/each}
|
|
445
|
+
{/if}
|
|
426
446
|
{/each}
|
|
427
447
|
</svg>
|
|
428
448
|
</div>
|
|
@@ -430,13 +450,28 @@
|
|
|
430
450
|
<ChartDataList {label} items={dataValueItems} />
|
|
431
451
|
|
|
432
452
|
{#if legend && legendItems.length > 0}
|
|
433
|
-
<ul class="st-comboChart__legend" aria-hidden="true">
|
|
453
|
+
<ul class="st-comboChart__legend" aria-hidden={legendInteractive ? undefined : "true"}>
|
|
434
454
|
{#each legendItems as item (item.key)}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
455
|
+
{@const off = hiddenSet.has(item.label)}
|
|
456
|
+
<li class="st-comboChart__legendItem" class:st-comboChart__legendItem--off={legendInteractive && off}>
|
|
457
|
+
{#if legendInteractive}
|
|
458
|
+
<button
|
|
459
|
+
type="button"
|
|
460
|
+
class="st-comboChart__legendButton"
|
|
461
|
+
aria-pressed={off}
|
|
462
|
+
onclick={() => onToggleSeries?.(item.label)}
|
|
463
|
+
>
|
|
464
|
+
<span
|
|
465
|
+
class="st-comboChart__legendSwatch st-comboChart__legendSwatch--{item.kind} st-comboChart__legendSwatch--{item.tone}"
|
|
466
|
+
></span>
|
|
467
|
+
{item.label}
|
|
468
|
+
</button>
|
|
469
|
+
{:else}
|
|
470
|
+
<span
|
|
471
|
+
class="st-comboChart__legendSwatch st-comboChart__legendSwatch--{item.kind} st-comboChart__legendSwatch--{item.tone}"
|
|
472
|
+
></span>
|
|
473
|
+
{item.label}
|
|
474
|
+
{/if}
|
|
440
475
|
</li>
|
|
441
476
|
{/each}
|
|
442
477
|
</ul>
|
|
@@ -567,6 +602,28 @@
|
|
|
567
602
|
gap: var(--st-spacing-1, 0.25rem);
|
|
568
603
|
}
|
|
569
604
|
|
|
605
|
+
.st-comboChart__legendItem--off {
|
|
606
|
+
opacity: 0.45;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.st-comboChart__legendButton {
|
|
610
|
+
align-items: center;
|
|
611
|
+
background: none;
|
|
612
|
+
border: 0;
|
|
613
|
+
border-radius: var(--st-radius-sm, 0.25rem);
|
|
614
|
+
color: inherit;
|
|
615
|
+
cursor: pointer;
|
|
616
|
+
display: inline-flex;
|
|
617
|
+
font: inherit;
|
|
618
|
+
gap: var(--st-spacing-1, 0.25rem);
|
|
619
|
+
padding: 0.125rem 0.25rem;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.st-comboChart__legendButton:focus-visible {
|
|
623
|
+
outline: 2px solid var(--st-semantic-border-interactive);
|
|
624
|
+
outline-offset: 2px;
|
|
625
|
+
}
|
|
626
|
+
|
|
570
627
|
.st-comboChart__legendSwatch {
|
|
571
628
|
display: inline-block;
|
|
572
629
|
flex: none;
|
|
@@ -17,6 +17,15 @@ type ComboChartProps = {
|
|
|
17
17
|
leftAxisLabel?: string;
|
|
18
18
|
rightAxisLabel?: string;
|
|
19
19
|
legend?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Interactive legend (FR-4). Ids/labels of bar/line series hidden from the
|
|
22
|
+
* render (controlled by the parent; default = all visible). Hidden series
|
|
23
|
+
* are omitted and their legend item is shown "off" (`aria-pressed`).
|
|
24
|
+
* Undefined → legacy non-interactive legend, unless `onToggleSeries` is set.
|
|
25
|
+
*/
|
|
26
|
+
hiddenSeries?: string[];
|
|
27
|
+
/** Emitted on click / Enter / Space on a legend item. */
|
|
28
|
+
onToggleSeries?: (seriesId: string) => void;
|
|
20
29
|
width?: number;
|
|
21
30
|
height?: number;
|
|
22
31
|
label: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComboChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/ComboChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAMF,KAAK,eAAe,GAAG;IACrB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC7B,KAAK,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;
|
|
1
|
+
{"version":3,"file":"ComboChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/ComboChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAMF,KAAK,eAAe,GAAG;IACrB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC7B,KAAK,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,yDAAyD;IACzD,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA6WJ,QAAA,MAAM,UAAU,qDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
package/dist/LineChart.svelte
CHANGED
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
type ChartAnnotation
|
|
61
61
|
} from "./chartAnnotations.js";
|
|
62
62
|
import { formatDataLabel, normalizeDataLabels, type DataLabelsProp } from "./chartDataLabels.js";
|
|
63
|
+
import { keyForX, resolveActiveIndex } from "./chartCrosshair.js";
|
|
63
64
|
|
|
64
65
|
type LineChartProps = {
|
|
65
66
|
data: LineChartDatum[];
|
|
@@ -111,6 +112,20 @@
|
|
|
111
112
|
* no legend surface, so this prop is accepted for parity and otherwise ignored.
|
|
112
113
|
*/
|
|
113
114
|
showLegend?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* CONTROLLED synchronised hover key (FR-3). A datum's key is `String(x)`. When
|
|
117
|
+
* provided (string or null), the crosshair + tooltip track this key instead of
|
|
118
|
+
* the chart's internal pointer hover (null ⇒ nothing shown), letting a parent
|
|
119
|
+
* share one hover channel across several aligned charts. Absent (`undefined`)
|
|
120
|
+
* keeps the legacy uncontrolled behaviour.
|
|
121
|
+
*/
|
|
122
|
+
hoverKey?: string | null;
|
|
123
|
+
/**
|
|
124
|
+
* Emitted when the user hovers a datum (its key) or leaves the plot (`null`).
|
|
125
|
+
* Always fired on pointer move/leave — even while CONTROLLED — so dataviz can
|
|
126
|
+
* keep the shared hover channel in sync.
|
|
127
|
+
*/
|
|
128
|
+
onHoverKeyChange?: (key: string | null) => void;
|
|
114
129
|
class?: string;
|
|
115
130
|
};
|
|
116
131
|
|
|
@@ -132,6 +147,8 @@
|
|
|
132
147
|
scale = "linear",
|
|
133
148
|
invertAxis = false,
|
|
134
149
|
showLegend,
|
|
150
|
+
hoverKey,
|
|
151
|
+
onHoverKeyChange,
|
|
135
152
|
class: className
|
|
136
153
|
}: LineChartProps = $props();
|
|
137
154
|
|
|
@@ -617,19 +634,33 @@
|
|
|
617
634
|
return entries;
|
|
618
635
|
});
|
|
619
636
|
|
|
637
|
+
// Stable key per datum (FR-3): `String(x)`. Resolves a controlled `hoverKey`
|
|
638
|
+
// to an index and feeds `onHoverKeyChange` from pointer events.
|
|
639
|
+
const hoverKeys = $derived(data.map((d) => keyForX(d.x)));
|
|
640
|
+
function emitHoverKey(index: number | null) {
|
|
641
|
+
onHoverKeyChange?.(index == null ? null : hoverKeys[index] ?? null);
|
|
642
|
+
}
|
|
620
643
|
function handleLeave() {
|
|
621
644
|
hoveredIndex = null;
|
|
645
|
+
emitHoverKey(null);
|
|
622
646
|
}
|
|
623
647
|
function handleVisualPointerMove(event: PointerEvent) {
|
|
624
648
|
const target = event.target;
|
|
625
649
|
if (!(target instanceof Element)) {
|
|
626
650
|
hoveredIndex = null;
|
|
651
|
+
emitHoverKey(null);
|
|
627
652
|
return;
|
|
628
653
|
}
|
|
629
|
-
const
|
|
630
|
-
|
|
654
|
+
const raw = Number(target.getAttribute("data-chart-index"));
|
|
655
|
+
const index = Number.isInteger(raw) ? raw : null;
|
|
656
|
+
hoveredIndex = index;
|
|
657
|
+
emitHoverKey(index);
|
|
631
658
|
}
|
|
632
659
|
|
|
660
|
+
// Index whose crosshair/tooltip is DISPLAYED: the controlled `hoverKey` when
|
|
661
|
+
// provided (resolved against `hoverKeys`), else the internal pointer index.
|
|
662
|
+
const activeIndex = $derived(resolveActiveIndex(hoverKey, hoveredIndex, hoverKeys));
|
|
663
|
+
|
|
633
664
|
const classes = () =>
|
|
634
665
|
["st-lineChart", `st-lineChart--${tone}`, className].filter(Boolean).join(" ");
|
|
635
666
|
</script>
|
|
@@ -820,13 +851,23 @@
|
|
|
820
851
|
{/each}
|
|
821
852
|
</g>
|
|
822
853
|
{/if}
|
|
854
|
+
|
|
855
|
+
<!-- Crosshair (FR-3) — a tokenised vertical line + marker at the active key.
|
|
856
|
+
Decorative (aria-hidden); the value is in the tooltip + ChartDataList. -->
|
|
857
|
+
{#if activeIndex >= 0 && points[activeIndex]}
|
|
858
|
+
{@const cp = points[activeIndex]}
|
|
859
|
+
<g class="st-lineChart__crosshair" aria-hidden="true">
|
|
860
|
+
<line class="st-lineChart__crosshairLine" x1={cp.x} x2={cp.x} y1={MARGIN.top} y2={MARGIN.top + plotHeight} />
|
|
861
|
+
<circle class="st-lineChart__crosshairMarker" cx={cp.x} cy={cp.y} r="5" />
|
|
862
|
+
</g>
|
|
863
|
+
{/if}
|
|
823
864
|
</svg>
|
|
824
865
|
</div>
|
|
825
866
|
|
|
826
867
|
<ChartDataList {label} items={dataValueItems} />
|
|
827
868
|
|
|
828
|
-
{#if
|
|
829
|
-
{@const p = points[
|
|
869
|
+
{#if activeIndex >= 0 && points[activeIndex]}
|
|
870
|
+
{@const p = points[activeIndex]}
|
|
830
871
|
<div
|
|
831
872
|
class="st-lineChart__tooltip"
|
|
832
873
|
role="presentation"
|
|
@@ -1028,4 +1069,19 @@
|
|
|
1028
1069
|
font-size: 0.6875rem;
|
|
1029
1070
|
font-weight: 600;
|
|
1030
1071
|
}
|
|
1072
|
+
|
|
1073
|
+
/* --- Crosshair layer (FR-3) ----------------------------------------------
|
|
1074
|
+
A tokenised dashed vertical line at the active (hovered/controlled) key,
|
|
1075
|
+
plus an emphasised marker on the point. Decorative (aria-hidden). */
|
|
1076
|
+
.st-lineChart__crosshairLine {
|
|
1077
|
+
stroke: var(--st-semantic-border-strong);
|
|
1078
|
+
stroke-width: 1;
|
|
1079
|
+
stroke-dasharray: 3 3;
|
|
1080
|
+
opacity: 0.7;
|
|
1081
|
+
}
|
|
1082
|
+
.st-lineChart__crosshairMarker {
|
|
1083
|
+
fill: currentColor;
|
|
1084
|
+
stroke: var(--st-semantic-surface-default);
|
|
1085
|
+
stroke-width: 2;
|
|
1086
|
+
}
|
|
1031
1087
|
</style>
|
|
@@ -86,6 +86,20 @@ type LineChartProps = {
|
|
|
86
86
|
* no legend surface, so this prop is accepted for parity and otherwise ignored.
|
|
87
87
|
*/
|
|
88
88
|
showLegend?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* CONTROLLED synchronised hover key (FR-3). A datum's key is `String(x)`. When
|
|
91
|
+
* provided (string or null), the crosshair + tooltip track this key instead of
|
|
92
|
+
* the chart's internal pointer hover (null ⇒ nothing shown), letting a parent
|
|
93
|
+
* share one hover channel across several aligned charts. Absent (`undefined`)
|
|
94
|
+
* keeps the legacy uncontrolled behaviour.
|
|
95
|
+
*/
|
|
96
|
+
hoverKey?: string | null;
|
|
97
|
+
/**
|
|
98
|
+
* Emitted when the user hovers a datum (its key) or leaves the plot (`null`).
|
|
99
|
+
* Always fired on pointer move/leave — even while CONTROLLED — so dataviz can
|
|
100
|
+
* keep the shared hover channel in sync.
|
|
101
|
+
*/
|
|
102
|
+
onHoverKeyChange?: (key: string | null) => void;
|
|
89
103
|
class?: string;
|
|
90
104
|
};
|
|
91
105
|
declare const LineChart: import("svelte").Component<LineChartProps, {}, "">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LineChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/LineChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,aAAa,GACrB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,cAAc,GAAG;IAC3B,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,CAAC;IACV;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,IAAI,CAAC,EAAE,GAAG,GAAG,GAAG,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,KAAK,CAAC;AAI5C,OAAO,EAIH,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AACjC,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"LineChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/LineChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,aAAa,GACrB,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,MAAM,cAAc,GAAG;IAC3B,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,CAAC;IACV;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,IAAI,CAAC,EAAE,GAAG,GAAG,GAAG,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,KAAK,CAAC;AAI5C,OAAO,EAIH,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AACjC,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI/F,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,cAAc,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACtC,oDAAoD;IACpD,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;IACpB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,qDAAqD;IACrD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1B;;;;OAIG;IACH,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,iDAAiD;IACjD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA+qBJ,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -33,6 +33,16 @@
|
|
|
33
33
|
* values already live in the accessible ChartDataList.
|
|
34
34
|
*/
|
|
35
35
|
dataLabels?: DataLabelsProp;
|
|
36
|
+
/**
|
|
37
|
+
* Interactive legend (FR-4). Ids/labels of series hidden from the render
|
|
38
|
+
* (controlled by the parent; default = all visible). Each segment whose
|
|
39
|
+
* `label` ∈ `hiddenSeries` is omitted and its legend item is shown "off"
|
|
40
|
+
* (`aria-pressed`). Undefined → legacy non-interactive legend, unless
|
|
41
|
+
* `onToggleSeries` is provided.
|
|
42
|
+
*/
|
|
43
|
+
hiddenSeries?: string[];
|
|
44
|
+
/** Emitted on click / Enter / Space on a legend item. */
|
|
45
|
+
onToggleSeries?: (seriesId: string) => void;
|
|
36
46
|
class?: string;
|
|
37
47
|
};
|
|
38
48
|
|
|
@@ -43,9 +53,15 @@
|
|
|
43
53
|
label,
|
|
44
54
|
showLegend = true,
|
|
45
55
|
dataLabels,
|
|
56
|
+
hiddenSeries,
|
|
57
|
+
onToggleSeries,
|
|
46
58
|
class: className
|
|
47
59
|
}: StackedBarChartProps = $props();
|
|
48
60
|
|
|
61
|
+
// Interactive legend is active as soon as the parent wires either prop.
|
|
62
|
+
const legendInteractive = $derived(onToggleSeries !== undefined || hiddenSeries !== undefined);
|
|
63
|
+
const hiddenSet = $derived(new Set(hiddenSeries ?? []));
|
|
64
|
+
|
|
49
65
|
const MARGIN = { top: 14, right: 16, bottom: 34, left: 44 };
|
|
50
66
|
const TONES = ["category1","category2","category3","category4","category5","category6","category7","category8"] as const;
|
|
51
67
|
|
|
@@ -81,7 +97,9 @@
|
|
|
81
97
|
let hovered: { bar: number; seg: number } | null = $state(null);
|
|
82
98
|
|
|
83
99
|
const scales = $derived.by(() => {
|
|
84
|
-
const totals = data.map((b) =>
|
|
100
|
+
const totals = data.map((b) =>
|
|
101
|
+
b.segments.reduce((s, x) => (hiddenSet.has(x.label) ? s : s + Math.max(x.value, 0)), 0)
|
|
102
|
+
);
|
|
85
103
|
const ticks = niceTicks(0, Math.max(0, ...totals));
|
|
86
104
|
return {
|
|
87
105
|
ticks, domainMax: ticks[ticks.length - 1],
|
|
@@ -98,23 +116,32 @@
|
|
|
98
116
|
return data.map((bar, bi) => {
|
|
99
117
|
const x = MARGIN.left + band * bi + (band - barWidth) / 2;
|
|
100
118
|
let acc = 0;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
119
|
+
// Tone is bound to the original segment index so it stays stable when a
|
|
120
|
+
// series is toggled off; hidden segments are dropped before stacking.
|
|
121
|
+
const segs = bar.segments
|
|
122
|
+
.map((seg, si) => ({ seg, tone: seg.tone ?? TONES[si % TONES.length] }))
|
|
123
|
+
.filter(({ seg }) => !hiddenSet.has(seg.label))
|
|
124
|
+
.map(({ seg, tone }) => {
|
|
125
|
+
const v = Math.max(seg.value, 0);
|
|
126
|
+
const yTop = MARGIN.top + scaleLinear(acc + v, 0, domainMax, plotH, 0);
|
|
127
|
+
const yBottom = MARGIN.top + scaleLinear(acc, 0, domainMax, plotH, 0);
|
|
128
|
+
acc += v;
|
|
129
|
+
return {
|
|
130
|
+
x, y: yTop, width: barWidth, height: Math.max(yBottom - yTop, 0),
|
|
131
|
+
seg, tone,
|
|
132
|
+
cx: x + barWidth / 2, cy: yTop + (yBottom - yTop) / 2
|
|
133
|
+
};
|
|
134
|
+
});
|
|
112
135
|
return { x, band, label: bar.label, segs, cxLabel: MARGIN.left + band * (bi + 0.5) };
|
|
113
136
|
});
|
|
114
137
|
});
|
|
115
138
|
|
|
116
139
|
const dataValueItems = $derived(
|
|
117
|
-
data.flatMap((bar) =>
|
|
140
|
+
data.flatMap((bar) =>
|
|
141
|
+
bar.segments
|
|
142
|
+
.filter((seg) => !hiddenSet.has(seg.label))
|
|
143
|
+
.map((seg) => `${bar.label}, ${seg.label}: ${seg.value}`)
|
|
144
|
+
)
|
|
118
145
|
);
|
|
119
146
|
|
|
120
147
|
// --- Data labels ----------------------------------------------------------
|
|
@@ -206,9 +233,22 @@
|
|
|
206
233
|
{#if showLegend && legend.length > 0}
|
|
207
234
|
<ul class="st-stackedBar__legend">
|
|
208
235
|
{#each legend as item (item.seriesLabel)}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
{
|
|
236
|
+
{@const off = hiddenSet.has(item.seriesLabel)}
|
|
237
|
+
<li class="st-stackedBar__legendItem" class:st-stackedBar__legendItem--off={legendInteractive && off}>
|
|
238
|
+
{#if legendInteractive}
|
|
239
|
+
<button
|
|
240
|
+
type="button"
|
|
241
|
+
class="st-stackedBar__legendButton"
|
|
242
|
+
aria-pressed={off}
|
|
243
|
+
onclick={() => onToggleSeries?.(item.seriesLabel)}
|
|
244
|
+
>
|
|
245
|
+
<span class="st-stackedBar__legendSwatch st-stackedBar__legendSwatch--{item.tone}" aria-hidden="true"></span>
|
|
246
|
+
{item.seriesLabel}
|
|
247
|
+
</button>
|
|
248
|
+
{:else}
|
|
249
|
+
<span class="st-stackedBar__legendSwatch st-stackedBar__legendSwatch--{item.tone}" aria-hidden="true"></span>
|
|
250
|
+
{item.seriesLabel}
|
|
251
|
+
{/if}
|
|
212
252
|
</li>
|
|
213
253
|
{/each}
|
|
214
254
|
</ul>
|
|
@@ -243,6 +283,12 @@
|
|
|
243
283
|
.st-stackedBar__tooltipValue { opacity: 0.85; }
|
|
244
284
|
.st-stackedBar__legend { display: flex; flex-wrap: wrap; gap: 0.75rem; list-style: none; margin: 0.5rem 0 0; padding: 0; }
|
|
245
285
|
.st-stackedBar__legendItem { align-items: center; color: var(--st-semantic-text-secondary); display: inline-flex; font-size: 0.75rem; gap: 0.35rem; }
|
|
286
|
+
.st-stackedBar__legendItem--off { opacity: 0.45; }
|
|
287
|
+
.st-stackedBar__legendButton {
|
|
288
|
+
align-items: center; background: none; border: 0; border-radius: var(--st-radius-sm, 0.25rem);
|
|
289
|
+
color: inherit; cursor: pointer; display: inline-flex; font: inherit; gap: 0.35rem; padding: 0.125rem 0.25rem;
|
|
290
|
+
}
|
|
291
|
+
.st-stackedBar__legendButton:focus-visible { outline: 2px solid var(--st-semantic-border-interactive); outline-offset: 2px; }
|
|
246
292
|
.st-stackedBar__legendSwatch { border-radius: 2px; height: 0.7rem; width: 0.7rem; }
|
|
247
293
|
.st-stackedBar__legendSwatch--category1 { background: var(--st-semantic-data-category1); }
|
|
248
294
|
.st-stackedBar__legendSwatch--category2 { background: var(--st-semantic-data-category2); }
|
|
@@ -23,6 +23,16 @@ type StackedBarChartProps = {
|
|
|
23
23
|
* values already live in the accessible ChartDataList.
|
|
24
24
|
*/
|
|
25
25
|
dataLabels?: DataLabelsProp;
|
|
26
|
+
/**
|
|
27
|
+
* Interactive legend (FR-4). Ids/labels of series hidden from the render
|
|
28
|
+
* (controlled by the parent; default = all visible). Each segment whose
|
|
29
|
+
* `label` ∈ `hiddenSeries` is omitted and its legend item is shown "off"
|
|
30
|
+
* (`aria-pressed`). Undefined → legacy non-interactive legend, unless
|
|
31
|
+
* `onToggleSeries` is provided.
|
|
32
|
+
*/
|
|
33
|
+
hiddenSeries?: string[];
|
|
34
|
+
/** Emitted on click / Enter / Space on a legend item. */
|
|
35
|
+
onToggleSeries?: (seriesId: string) => void;
|
|
26
36
|
class?: string;
|
|
27
37
|
};
|
|
28
38
|
declare const StackedBarChart: import("svelte").Component<StackedBarChartProps, {}, "">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StackedBarChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/StackedBarChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,cAAc,GACtB,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GACrD,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;AAE1D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B,CAAC;AAIJ,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG/F,KAAK,oBAAoB,GAAG;IAC1B,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;
|
|
1
|
+
{"version":3,"file":"StackedBarChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/StackedBarChart.svelte.ts"],"names":[],"mappings":"AAGE,MAAM,MAAM,cAAc,GACtB,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GACrD,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;AAE1D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B,CAAC;AAIJ,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG/F,KAAK,oBAAoB,GAAG;IAC1B,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,yDAAyD;IACzD,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA4MJ,QAAA,MAAM,eAAe,0DAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Serialises a Line/Area datum's `x` to its stable hover key. */
|
|
2
|
+
export declare function keyForX(x: number | string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the hover key to a datum index within `keys` (the ordered list of
|
|
5
|
+
* every datum's key). Returns -1 when the key is null/undefined or unmatched.
|
|
6
|
+
*/
|
|
7
|
+
export declare function indexForHoverKey(hoverKey: string | null | undefined, keys: string[]): number;
|
|
8
|
+
/**
|
|
9
|
+
* Picks the datum index to DISPLAY the crosshair/tooltip at.
|
|
10
|
+
* - Controlled (`hoverKey !== undefined`): the index of `hoverKey` in `keys`
|
|
11
|
+
* (or -1 when null/unmatched). The internal pointer index is ignored for
|
|
12
|
+
* display.
|
|
13
|
+
* - Uncontrolled (`hoverKey === undefined`): the internal pointer index.
|
|
14
|
+
* Returns -1 when nothing should be shown.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveActiveIndex(hoverKey: string | null | undefined, internalIndex: number | null, keys: string[]): number;
|
|
17
|
+
/** True when the chart is CONTROLLED (the parent supplied `hoverKey`). */
|
|
18
|
+
export declare function isControlled(hoverKey: string | null | undefined): boolean;
|
|
19
|
+
//# sourceMappingURL=chartCrosshair.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chartCrosshair.d.ts","sourceRoot":"","sources":["../src/lib/chartCrosshair.ts"],"names":[],"mappings":"AAsBA,kEAAkE;AAClE,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAElD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAG5F;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,IAAI,EAAE,MAAM,EAAE,GACb,MAAM,CAGR;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAEzE"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// --- Chart crosshair / synchronised hover layer (shared, framework-agnostic) -
|
|
2
|
+
//
|
|
3
|
+
// FR-3: a CONTROLLED crosshair + tooltip whose position is driven by a `hoverKey`
|
|
4
|
+
// string the parent owns, so dataviz can share one hover channel across several
|
|
5
|
+
// aligned panels (a "linked" tooltip). The DS stays presentational: it resolves
|
|
6
|
+
// the key to a datum index, draws a tokenised vertical line at that x (plus a
|
|
7
|
+
// marker on the point for Line/Area), and reuses the existing tooltip surface.
|
|
8
|
+
//
|
|
9
|
+
// The "key" is the stable identifier of a datum on the categorical/x axis:
|
|
10
|
+
// - Bar: the bar's `label`.
|
|
11
|
+
// - Line/Area: the point's `x`, serialised with `String(x)`.
|
|
12
|
+
//
|
|
13
|
+
// Behaviour:
|
|
14
|
+
// - `hoverKey === undefined` → UNCONTROLLED (internal hover, unchanged, fully
|
|
15
|
+
// backward compatible).
|
|
16
|
+
// - `hoverKey` provided (string or null) → CONTROLLED: the displayed
|
|
17
|
+
// crosshair/tooltip tracks `hoverKey` (null = nothing shown), the chart's own
|
|
18
|
+
// pointer hover no longer drives the DISPLAY, but `onHoverKeyChange` is still
|
|
19
|
+
// emitted so the parent can keep the shared channel in sync.
|
|
20
|
+
//
|
|
21
|
+
// Purely additive: a chart that passes neither prop renders exactly as before.
|
|
22
|
+
/** Serialises a Line/Area datum's `x` to its stable hover key. */
|
|
23
|
+
export function keyForX(x) {
|
|
24
|
+
return String(x);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolves the hover key to a datum index within `keys` (the ordered list of
|
|
28
|
+
* every datum's key). Returns -1 when the key is null/undefined or unmatched.
|
|
29
|
+
*/
|
|
30
|
+
export function indexForHoverKey(hoverKey, keys) {
|
|
31
|
+
if (hoverKey == null)
|
|
32
|
+
return -1;
|
|
33
|
+
return keys.indexOf(hoverKey);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Picks the datum index to DISPLAY the crosshair/tooltip at.
|
|
37
|
+
* - Controlled (`hoverKey !== undefined`): the index of `hoverKey` in `keys`
|
|
38
|
+
* (or -1 when null/unmatched). The internal pointer index is ignored for
|
|
39
|
+
* display.
|
|
40
|
+
* - Uncontrolled (`hoverKey === undefined`): the internal pointer index.
|
|
41
|
+
* Returns -1 when nothing should be shown.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveActiveIndex(hoverKey, internalIndex, keys) {
|
|
44
|
+
if (hoverKey !== undefined)
|
|
45
|
+
return indexForHoverKey(hoverKey, keys);
|
|
46
|
+
return internalIndex == null ? -1 : internalIndex;
|
|
47
|
+
}
|
|
48
|
+
/** True when the chart is CONTROLLED (the parent supplied `hoverKey`). */
|
|
49
|
+
export function isControlled(hoverKey) {
|
|
50
|
+
return hoverKey !== undefined;
|
|
51
|
+
}
|