@sentropic/design-system-svelte 0.34.22 → 0.34.24
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 +160 -1
- package/dist/AreaChart.svelte.d.ts +16 -0
- package/dist/AreaChart.svelte.d.ts.map +1 -1
- package/dist/BarChart.svelte +191 -1
- package/dist/BarChart.svelte.d.ts +18 -0
- package/dist/BarChart.svelte.d.ts.map +1 -1
- package/dist/DonutChart.svelte +52 -2
- package/dist/DonutChart.svelte.d.ts +9 -0
- package/dist/DonutChart.svelte.d.ts.map +1 -1
- package/dist/LineChart.svelte +162 -1
- package/dist/LineChart.svelte.d.ts +17 -0
- package/dist/LineChart.svelte.d.ts.map +1 -1
- package/dist/StackedBarChart.svelte +44 -0
- package/dist/StackedBarChart.svelte.d.ts +9 -0
- package/dist/StackedBarChart.svelte.d.ts.map +1 -1
- package/dist/chartAnnotations.d.ts +132 -0
- package/dist/chartAnnotations.d.ts.map +1 -0
- package/dist/chartAnnotations.js +125 -0
- package/dist/chartDataLabels.d.ts +35 -0
- package/dist/chartDataLabels.d.ts.map +1 -0
- package/dist/chartDataLabels.js +31 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/AreaChart.svelte
CHANGED
|
@@ -17,6 +17,13 @@
|
|
|
17
17
|
|
|
18
18
|
<script lang="ts">
|
|
19
19
|
import ChartDataList from "./ChartDataList.svelte";
|
|
20
|
+
import {
|
|
21
|
+
resolveAnnotations,
|
|
22
|
+
annotationDataListItems,
|
|
23
|
+
polygonPoints,
|
|
24
|
+
type ChartAnnotation
|
|
25
|
+
} from "./chartAnnotations.js";
|
|
26
|
+
import { formatDataLabel, normalizeDataLabels, type DataLabelsProp } from "./chartDataLabels.js";
|
|
20
27
|
|
|
21
28
|
type AreaChartProps = {
|
|
22
29
|
data: (number | AreaChartDatum)[];
|
|
@@ -25,6 +32,20 @@
|
|
|
25
32
|
tone?: AreaChartTone;
|
|
26
33
|
smooth?: boolean;
|
|
27
34
|
label: string;
|
|
35
|
+
/**
|
|
36
|
+
* Annotation overlay in DATA space (points, labels, axis lines, regions,
|
|
37
|
+
* polygons), resolved to pixels via the chart scales. Regions render behind
|
|
38
|
+
* the area, every other kind above it. Additive: absent ⇒ unchanged.
|
|
39
|
+
*/
|
|
40
|
+
annotations?: ChartAnnotation[];
|
|
41
|
+
/**
|
|
42
|
+
* Per-point value labels. `false`/absent (default) → none. `true` → each
|
|
43
|
+
* point's value with the chart's numeric formatter. Object → `format(value)`
|
|
44
|
+
* and/or a `position` override. Default position is `top` (above the point).
|
|
45
|
+
* Labels are `aria-hidden` — the values already live in the accessible
|
|
46
|
+
* ChartDataList.
|
|
47
|
+
*/
|
|
48
|
+
dataLabels?: DataLabelsProp;
|
|
28
49
|
class?: string;
|
|
29
50
|
};
|
|
30
51
|
|
|
@@ -35,6 +56,8 @@
|
|
|
35
56
|
tone = "category1",
|
|
36
57
|
smooth = false,
|
|
37
58
|
label,
|
|
59
|
+
annotations,
|
|
60
|
+
dataLabels,
|
|
38
61
|
class: className
|
|
39
62
|
}: AreaChartProps = $props();
|
|
40
63
|
|
|
@@ -87,7 +110,10 @@
|
|
|
87
110
|
});
|
|
88
111
|
});
|
|
89
112
|
|
|
90
|
-
const dataValueItems = $derived(
|
|
113
|
+
const dataValueItems = $derived([
|
|
114
|
+
...normalizedData.map((d) => `${d.x}: ${d.y}`),
|
|
115
|
+
...annotationDataListItems(annotations)
|
|
116
|
+
]);
|
|
91
117
|
|
|
92
118
|
let hoveredIndex: number | null = $state(null);
|
|
93
119
|
|
|
@@ -139,6 +165,50 @@
|
|
|
139
165
|
});
|
|
140
166
|
});
|
|
141
167
|
|
|
168
|
+
// --- Annotation overlay ---------------------------------------------------
|
|
169
|
+
// `xScale` honours the ordinal/numeric x domain; `yScale` mirrors the point
|
|
170
|
+
// y mapping. Out-of-domain coordinates yield `null` → the resolver drops them.
|
|
171
|
+
const annotationXScale = $derived((v: number | string): number | null => {
|
|
172
|
+
if (xDomain.kind === "numeric") {
|
|
173
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return null;
|
|
174
|
+
if (v < xDomain.min || v > xDomain.max) return null;
|
|
175
|
+
return scaleLinear(v, xDomain.min, xDomain.max, 0, plotWidth);
|
|
176
|
+
}
|
|
177
|
+
const i = normalizedData.findIndex((d) => d.x === v);
|
|
178
|
+
if (i < 0) return null;
|
|
179
|
+
const denom = Math.max(normalizedData.length - 1, 1);
|
|
180
|
+
return normalizedData.length === 1 ? plotWidth / 2 : (i / denom) * plotWidth;
|
|
181
|
+
});
|
|
182
|
+
const annotationYScale = $derived((v: number): number | null => {
|
|
183
|
+
if (!Number.isFinite(v) || v < yDomain.min || v > yDomain.max) return null;
|
|
184
|
+
return scaleLinear(v, yDomain.min, yDomain.max, plotHeight, 0);
|
|
185
|
+
});
|
|
186
|
+
const resolvedAnnotations = $derived(
|
|
187
|
+
resolveAnnotations(annotations, {
|
|
188
|
+
xScale: annotationXScale,
|
|
189
|
+
yScale: annotationYScale,
|
|
190
|
+
plotLeft: MARGIN.left,
|
|
191
|
+
plotTop: MARGIN.top,
|
|
192
|
+
plotWidth,
|
|
193
|
+
plotHeight
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
const annotationRegions = $derived(resolvedAnnotations.filter((a) => a.kind === "region"));
|
|
197
|
+
const annotationAbove = $derived(resolvedAnnotations.filter((a) => a.kind !== "region"));
|
|
198
|
+
|
|
199
|
+
// --- Data labels ----------------------------------------------------------
|
|
200
|
+
// One value label per point. Default `top`: just above the dot. `center` sits
|
|
201
|
+
// on the dot. aria-hidden (values are in the ChartDataList already).
|
|
202
|
+
const dataLabelOpts = $derived(normalizeDataLabels(dataLabels));
|
|
203
|
+
const dataLabelItems = $derived.by(() => {
|
|
204
|
+
if (!dataLabelOpts.enabled) return [] as { key: number; x: number; y: number; text: string; baseline: string }[];
|
|
205
|
+
return points.map((p) => {
|
|
206
|
+
const text = formatDataLabel(p.datum.y, dataLabelOpts, formatTick);
|
|
207
|
+
const center = dataLabelOpts.position === "center" || dataLabelOpts.position === "inside";
|
|
208
|
+
return { key: p.index, x: p.x, y: center ? p.y : p.y - 8, text, baseline: center ? "middle" : "auto" };
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
142
212
|
function buildLinearPath(pts: { x: number; y: number }[]): string {
|
|
143
213
|
return pts.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(2)},${p.y.toFixed(2)}`).join(" ");
|
|
144
214
|
}
|
|
@@ -294,6 +364,20 @@
|
|
|
294
364
|
</text>
|
|
295
365
|
{/each}
|
|
296
366
|
|
|
367
|
+
<!-- Annotation regions sit BEHIND the area. -->
|
|
368
|
+
{#if annotationRegions.length > 0}
|
|
369
|
+
<g class="st-areaChart__annotations st-areaChart__annotations--behind">
|
|
370
|
+
{#each annotationRegions as a (a.key)}
|
|
371
|
+
{#if a.kind === "region"}
|
|
372
|
+
<rect class="st-areaChart__annotationRegion" x={a.x} y={a.y} width={a.width} height={a.height} />
|
|
373
|
+
{#if a.label}
|
|
374
|
+
<text class="st-areaChart__annotationLabel" x={a.x + 4} y={a.y + 11}>{a.label}</text>
|
|
375
|
+
{/if}
|
|
376
|
+
{/if}
|
|
377
|
+
{/each}
|
|
378
|
+
</g>
|
|
379
|
+
{/if}
|
|
380
|
+
|
|
297
381
|
{#if areaPath}
|
|
298
382
|
<path class="st-areaChart__area" d={areaPath} fill="url(#{gradientId})" />
|
|
299
383
|
{/if}
|
|
@@ -317,6 +401,46 @@
|
|
|
317
401
|
data-chart-index={p.index}
|
|
318
402
|
/>
|
|
319
403
|
{/each}
|
|
404
|
+
|
|
405
|
+
<!-- Annotations ABOVE the area: lines, shapes, points, labels. -->
|
|
406
|
+
{#if annotationAbove.length > 0}
|
|
407
|
+
<g class="st-areaChart__annotations st-areaChart__annotations--above">
|
|
408
|
+
{#each annotationAbove as a (a.key)}
|
|
409
|
+
{#if a.kind === "line"}
|
|
410
|
+
<line class="st-areaChart__annotationLine" x1={a.x1} y1={a.y1} x2={a.x2} y2={a.y2} />
|
|
411
|
+
{#if a.label}
|
|
412
|
+
<text
|
|
413
|
+
class="st-areaChart__annotationLabel"
|
|
414
|
+
x={a.axis === "x" ? a.x1 + 4 : MARGIN.left + plotWidth - 4}
|
|
415
|
+
y={a.axis === "x" ? MARGIN.top + 11 : a.y1 - 4}
|
|
416
|
+
text-anchor={a.axis === "x" ? "start" : "end"}
|
|
417
|
+
>{a.label}</text>
|
|
418
|
+
{/if}
|
|
419
|
+
{:else if a.kind === "shape"}
|
|
420
|
+
<polygon class="st-areaChart__annotationShape" points={polygonPoints(a.points)} />
|
|
421
|
+
{#if a.label}
|
|
422
|
+
<text class="st-areaChart__annotationLabel" x={a.labelX} y={a.labelY} text-anchor="middle">{a.label}</text>
|
|
423
|
+
{/if}
|
|
424
|
+
{:else if a.kind === "point"}
|
|
425
|
+
<circle class="st-areaChart__annotationPoint" cx={a.x} cy={a.y} r="4.5" />
|
|
426
|
+
{#if a.label}
|
|
427
|
+
<text class="st-areaChart__annotationLabel" x={a.x} y={a.y - 8} text-anchor="middle">{a.label}</text>
|
|
428
|
+
{/if}
|
|
429
|
+
{:else}
|
|
430
|
+
<text class="st-areaChart__annotationText" x={a.x} y={a.y} text-anchor={a.anchor}>{a.text}</text>
|
|
431
|
+
{/if}
|
|
432
|
+
{/each}
|
|
433
|
+
</g>
|
|
434
|
+
{/if}
|
|
435
|
+
|
|
436
|
+
<!-- Data labels — one value per point, drawn on top. aria-hidden. -->
|
|
437
|
+
{#if dataLabelItems.length > 0}
|
|
438
|
+
<g class="st-areaChart__dataLabels" aria-hidden="true">
|
|
439
|
+
{#each dataLabelItems as d (d.key)}
|
|
440
|
+
<text class="st-areaChart__dataLabel" x={d.x} y={d.y} text-anchor="middle" dominant-baseline={d.baseline}>{d.text}</text>
|
|
441
|
+
{/each}
|
|
442
|
+
</g>
|
|
443
|
+
{/if}
|
|
320
444
|
</svg>
|
|
321
445
|
</div>
|
|
322
446
|
|
|
@@ -429,4 +553,39 @@
|
|
|
429
553
|
.st-areaChart__tooltipValue {
|
|
430
554
|
opacity: 0.85;
|
|
431
555
|
}
|
|
556
|
+
|
|
557
|
+
/* --- Annotation layer ----------------------------------------------------
|
|
558
|
+
Regions render BEHIND the area; lines/shapes/points/labels render ABOVE. */
|
|
559
|
+
.st-areaChart__annotationRegion {
|
|
560
|
+
fill: color-mix(in srgb, var(--st-semantic-feedback-info) 12%, transparent);
|
|
561
|
+
stroke: none;
|
|
562
|
+
}
|
|
563
|
+
.st-areaChart__annotationLine {
|
|
564
|
+
stroke: var(--st-semantic-feedback-info);
|
|
565
|
+
stroke-width: 1.5;
|
|
566
|
+
stroke-dasharray: 4 3;
|
|
567
|
+
}
|
|
568
|
+
.st-areaChart__annotationShape {
|
|
569
|
+
fill: color-mix(in srgb, var(--st-semantic-feedback-info) 14%, transparent);
|
|
570
|
+
stroke: var(--st-semantic-feedback-info);
|
|
571
|
+
stroke-width: 1.5;
|
|
572
|
+
}
|
|
573
|
+
.st-areaChart__annotationPoint {
|
|
574
|
+
fill: var(--st-semantic-feedback-info);
|
|
575
|
+
stroke: var(--st-semantic-surface-default);
|
|
576
|
+
stroke-width: 1.5;
|
|
577
|
+
}
|
|
578
|
+
.st-areaChart__annotationLabel,
|
|
579
|
+
.st-areaChart__annotationText {
|
|
580
|
+
fill: var(--st-semantic-text-primary);
|
|
581
|
+
font-size: 0.625rem;
|
|
582
|
+
font-weight: 600;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/* Data labels — per-point value, drawn on top. Token-only colour. */
|
|
586
|
+
.st-areaChart__dataLabel {
|
|
587
|
+
fill: var(--st-semantic-text-primary);
|
|
588
|
+
font-size: 0.6875rem;
|
|
589
|
+
font-weight: 600;
|
|
590
|
+
}
|
|
432
591
|
</style>
|
|
@@ -3,6 +3,8 @@ export type AreaChartDatum = {
|
|
|
3
3
|
x: number | string;
|
|
4
4
|
y: number;
|
|
5
5
|
};
|
|
6
|
+
import { type ChartAnnotation } from "./chartAnnotations.js";
|
|
7
|
+
import { type DataLabelsProp } from "./chartDataLabels.js";
|
|
6
8
|
type AreaChartProps = {
|
|
7
9
|
data: (number | AreaChartDatum)[];
|
|
8
10
|
width?: number;
|
|
@@ -10,6 +12,20 @@ type AreaChartProps = {
|
|
|
10
12
|
tone?: AreaChartTone;
|
|
11
13
|
smooth?: boolean;
|
|
12
14
|
label: string;
|
|
15
|
+
/**
|
|
16
|
+
* Annotation overlay in DATA space (points, labels, axis lines, regions,
|
|
17
|
+
* polygons), resolved to pixels via the chart scales. Regions render behind
|
|
18
|
+
* the area, every other kind above it. Additive: absent ⇒ unchanged.
|
|
19
|
+
*/
|
|
20
|
+
annotations?: ChartAnnotation[];
|
|
21
|
+
/**
|
|
22
|
+
* Per-point value labels. `false`/absent (default) → none. `true` → each
|
|
23
|
+
* point's value with the chart's numeric formatter. Object → `format(value)`
|
|
24
|
+
* and/or a `position` override. Default position is `top` (above the point).
|
|
25
|
+
* Labels are `aria-hidden` — the values already live in the accessible
|
|
26
|
+
* ChartDataList.
|
|
27
|
+
*/
|
|
28
|
+
dataLabels?: DataLabelsProp;
|
|
13
29
|
class?: string;
|
|
14
30
|
};
|
|
15
31
|
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;
|
|
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;AAG/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,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAuWJ,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
package/dist/BarChart.svelte
CHANGED
|
@@ -50,6 +50,13 @@
|
|
|
50
50
|
|
|
51
51
|
<script lang="ts">
|
|
52
52
|
import ChartDataList from "./ChartDataList.svelte";
|
|
53
|
+
import {
|
|
54
|
+
resolveAnnotations,
|
|
55
|
+
annotationDataListItems,
|
|
56
|
+
polygonPoints,
|
|
57
|
+
type ChartAnnotation
|
|
58
|
+
} from "./chartAnnotations.js";
|
|
59
|
+
import { formatDataLabel, normalizeDataLabels, type DataLabelsProp } from "./chartDataLabels.js";
|
|
53
60
|
|
|
54
61
|
type BarChartProps = {
|
|
55
62
|
data: BarChartDatum[];
|
|
@@ -86,6 +93,22 @@
|
|
|
86
93
|
bands?: ChartBand[];
|
|
87
94
|
/** A single goal line, emphasised above the bars. */
|
|
88
95
|
goalLine?: ChartGoalLine;
|
|
96
|
+
/**
|
|
97
|
+
* Annotation overlay in DATA space. The x coordinate is CATEGORICAL — it
|
|
98
|
+
* matches a bar by its `label` (centre of band) and is ignored otherwise; the
|
|
99
|
+
* y coordinate (and `value`/`from`/`to`) are value-axis numbers. Regions
|
|
100
|
+
* render behind the bars, every other kind above. Additive: absent ⇒
|
|
101
|
+
* unchanged.
|
|
102
|
+
*/
|
|
103
|
+
annotations?: ChartAnnotation[];
|
|
104
|
+
/**
|
|
105
|
+
* Per-bar value labels. `false`/absent (default) → none. `true` → each bar's
|
|
106
|
+
* value with the chart's numeric formatter. Object → `format(value)` and/or
|
|
107
|
+
* a `position` override. Default position is `outside` (above the bar in
|
|
108
|
+
* vertical mode, past the bar end in horizontal mode). Labels are
|
|
109
|
+
* `aria-hidden` — the values already live in the accessible ChartDataList.
|
|
110
|
+
*/
|
|
111
|
+
dataLabels?: DataLabelsProp;
|
|
89
112
|
/**
|
|
90
113
|
* Value-axis scale. `"linear"` (default) is unchanged. `"log"` switches the
|
|
91
114
|
* value axis to a base-10 logarithmic scale — values `<= 0` are ignored for
|
|
@@ -116,6 +139,8 @@
|
|
|
116
139
|
referenceLines,
|
|
117
140
|
bands,
|
|
118
141
|
goalLine,
|
|
142
|
+
annotations,
|
|
143
|
+
dataLabels,
|
|
119
144
|
scale = "linear",
|
|
120
145
|
invertAxis = false,
|
|
121
146
|
showLegend,
|
|
@@ -469,9 +494,85 @@
|
|
|
469
494
|
return out;
|
|
470
495
|
});
|
|
471
496
|
|
|
497
|
+
// --- Annotation overlay ---------------------------------------------------
|
|
498
|
+
// The x coordinate is CATEGORICAL (a bar `label` → centre of band); the y
|
|
499
|
+
// coordinate is a value-axis number. For a HORIZONTAL chart the value axis is
|
|
500
|
+
// x and the category axis is y, so the data-space (x=category, y=value)
|
|
501
|
+
// convention is transposed onto the pixel axes: the resolver's `xScale` maps
|
|
502
|
+
// the value, `yScale` the category, and each annotation's `axis`/coords are
|
|
503
|
+
// swapped so a value reference (`axis: "y"`) still spans the full plot.
|
|
504
|
+
const categoryPixel = $derived((v: number | string): number | null => {
|
|
505
|
+
const bar = bars.find((b) => b.datum.label === String(v));
|
|
506
|
+
if (!bar) return null;
|
|
507
|
+
return isVertical ? bar.cx - MARGIN.left : bar.cy - MARGIN.top;
|
|
508
|
+
});
|
|
509
|
+
const valuePixelRel = $derived((v: number): number | null => {
|
|
510
|
+
if (!Number.isFinite(v)) return null;
|
|
511
|
+
return valuePos(v) - (isVertical ? MARGIN.top : MARGIN.left);
|
|
512
|
+
});
|
|
513
|
+
const transposeAnnotations = $derived((list: ChartAnnotation[] | undefined): ChartAnnotation[] | undefined => {
|
|
514
|
+
if (!list || isVertical) return list;
|
|
515
|
+
// Horizontal: swap the data-space x/y (and the region/line axis) so the
|
|
516
|
+
// generic resolver — which always emits xScale→x-pixel, yScale→y-pixel —
|
|
517
|
+
// lands the value on the x axis and the category on the y axis.
|
|
518
|
+
return list.map((a): ChartAnnotation => {
|
|
519
|
+
switch (a.kind) {
|
|
520
|
+
case "line":
|
|
521
|
+
return { ...a, axis: a.axis === "x" ? "y" : "x" };
|
|
522
|
+
case "region":
|
|
523
|
+
return { ...a, axis: a.axis === "x" ? "y" : "x" };
|
|
524
|
+
case "point":
|
|
525
|
+
return { ...a, x: a.y, y: typeof a.x === "number" ? a.x : NaN } as ChartAnnotation;
|
|
526
|
+
case "label":
|
|
527
|
+
return { ...a, x: a.y, y: typeof a.x === "number" ? a.x : NaN } as ChartAnnotation;
|
|
528
|
+
case "shape":
|
|
529
|
+
return { ...a, points: a.points.map((p) => ({ x: p.y, y: typeof p.x === "number" ? p.x : NaN })) };
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
const annXScale = $derived((v: number | string): number | null =>
|
|
534
|
+
isVertical ? categoryPixel(v) : typeof v === "number" ? valuePixelRel(v) : null
|
|
535
|
+
);
|
|
536
|
+
const annYScale = $derived((v: number): number | null => (isVertical ? valuePixelRel(v) : categoryPixel(v)));
|
|
537
|
+
const resolvedAnnotations = $derived(
|
|
538
|
+
resolveAnnotations(transposeAnnotations(annotations), {
|
|
539
|
+
xScale: annXScale,
|
|
540
|
+
yScale: annYScale,
|
|
541
|
+
plotLeft: MARGIN.left,
|
|
542
|
+
plotTop: MARGIN.top,
|
|
543
|
+
plotWidth: scales.plotWidth,
|
|
544
|
+
plotHeight: scales.plotHeight
|
|
545
|
+
})
|
|
546
|
+
);
|
|
547
|
+
const annotationRegions = $derived(resolvedAnnotations.filter((a) => a.kind === "region"));
|
|
548
|
+
const annotationAbove = $derived(resolvedAnnotations.filter((a) => a.kind !== "region"));
|
|
549
|
+
|
|
550
|
+
// --- Data labels ----------------------------------------------------------
|
|
551
|
+
// One value label per bar, placed at the bar's value end. Default `outside`:
|
|
552
|
+
// above the bar (vertical) / past the bar end (horizontal). `inside`/`center`
|
|
553
|
+
// sit at the bar's mid-length. aria-hidden (values are in the ChartDataList).
|
|
554
|
+
const dataLabelOpts = $derived(normalizeDataLabels(dataLabels));
|
|
555
|
+
const dataLabelItems = $derived.by(() => {
|
|
556
|
+
if (!dataLabelOpts.enabled) return [] as { key: string; x: number; y: number; text: string; anchor: "start" | "middle" | "end"; baseline: string }[];
|
|
557
|
+
return bars.map((bar) => {
|
|
558
|
+
const text = formatDataLabel(bar.datum.value, dataLabelOpts, formatTick);
|
|
559
|
+
const pos = dataLabelOpts.position ?? "outside";
|
|
560
|
+
const inside = pos === "inside" || pos === "center";
|
|
561
|
+
if (isVertical) {
|
|
562
|
+
const x = bar.cx;
|
|
563
|
+
const y = inside ? bar.y + bar.height / 2 : bar.cy - 6;
|
|
564
|
+
return { key: bar.datum.label, x, y, text, anchor: "middle" as const, baseline: inside ? "middle" : "auto" };
|
|
565
|
+
}
|
|
566
|
+
const y = bar.cy;
|
|
567
|
+
const x = inside ? bar.x + bar.width / 2 : bar.cx + 4;
|
|
568
|
+
return { key: bar.datum.label, x, y, text, anchor: inside ? ("middle" as const) : ("start" as const), baseline: "middle" };
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
472
572
|
const dataValueItems = $derived([
|
|
473
573
|
...data.map((d) => `${d.label}: ${d.value}`),
|
|
474
|
-
...overlayDataListItems(referenceLines, bands, goal)
|
|
574
|
+
...overlayDataListItems(referenceLines, bands, goal),
|
|
575
|
+
...annotationDataListItems(annotations)
|
|
475
576
|
]);
|
|
476
577
|
|
|
477
578
|
const valueAxisTicks = $derived.by(() => {
|
|
@@ -630,6 +731,20 @@
|
|
|
630
731
|
{/if}
|
|
631
732
|
{/each}
|
|
632
733
|
|
|
734
|
+
<!-- Annotation regions sit BEHIND the bars (filled bands). -->
|
|
735
|
+
{#if annotationRegions.length > 0}
|
|
736
|
+
<g class="st-barChart__annotations st-barChart__annotations--behind">
|
|
737
|
+
{#each annotationRegions as a (a.key)}
|
|
738
|
+
{#if a.kind === "region"}
|
|
739
|
+
<rect class="st-barChart__annotationRegion" x={a.x} y={a.y} width={a.width} height={a.height} />
|
|
740
|
+
{#if a.label}
|
|
741
|
+
<text class="st-barChart__annotationLabel" x={a.x + 4} y={a.y + 11}>{a.label}</text>
|
|
742
|
+
{/if}
|
|
743
|
+
{/if}
|
|
744
|
+
{/each}
|
|
745
|
+
</g>
|
|
746
|
+
{/if}
|
|
747
|
+
|
|
633
748
|
<!-- bars -->
|
|
634
749
|
<!-- The bars live inside an aria-hidden SVG, so they are NEVER an accessible
|
|
635
750
|
surface. When `onSelect` is provided they only carry a mouse click
|
|
@@ -681,6 +796,46 @@
|
|
|
681
796
|
text-anchor={isVertical ? "end" : "start"}
|
|
682
797
|
>{goalGeom.label ?? `Objectif ${goalGeom.value}`}</text>
|
|
683
798
|
{/if}
|
|
799
|
+
|
|
800
|
+
<!-- Annotations ABOVE the bars: lines, shapes, points, labels. -->
|
|
801
|
+
{#if annotationAbove.length > 0}
|
|
802
|
+
<g class="st-barChart__annotations st-barChart__annotations--above">
|
|
803
|
+
{#each annotationAbove as a (a.key)}
|
|
804
|
+
{#if a.kind === "line"}
|
|
805
|
+
<line class="st-barChart__annotationLine" x1={a.x1} y1={a.y1} x2={a.x2} y2={a.y2} />
|
|
806
|
+
{#if a.label}
|
|
807
|
+
<text
|
|
808
|
+
class="st-barChart__annotationLabel"
|
|
809
|
+
x={a.axis === "x" ? a.x1 + 4 : MARGIN.left + scales.plotWidth - 4}
|
|
810
|
+
y={a.axis === "x" ? MARGIN.top + 11 : a.y1 - 4}
|
|
811
|
+
text-anchor={a.axis === "x" ? "start" : "end"}
|
|
812
|
+
>{a.label}</text>
|
|
813
|
+
{/if}
|
|
814
|
+
{:else if a.kind === "shape"}
|
|
815
|
+
<polygon class="st-barChart__annotationShape" points={polygonPoints(a.points)} />
|
|
816
|
+
{#if a.label}
|
|
817
|
+
<text class="st-barChart__annotationLabel" x={a.labelX} y={a.labelY} text-anchor="middle">{a.label}</text>
|
|
818
|
+
{/if}
|
|
819
|
+
{:else if a.kind === "point"}
|
|
820
|
+
<circle class="st-barChart__annotationPoint" cx={a.x} cy={a.y} r="4.5" />
|
|
821
|
+
{#if a.label}
|
|
822
|
+
<text class="st-barChart__annotationLabel" x={a.x} y={a.y - 8} text-anchor="middle">{a.label}</text>
|
|
823
|
+
{/if}
|
|
824
|
+
{:else}
|
|
825
|
+
<text class="st-barChart__annotationText" x={a.x} y={a.y} text-anchor={a.anchor}>{a.text}</text>
|
|
826
|
+
{/if}
|
|
827
|
+
{/each}
|
|
828
|
+
</g>
|
|
829
|
+
{/if}
|
|
830
|
+
|
|
831
|
+
<!-- Data labels — one value per bar, drawn on top. aria-hidden. -->
|
|
832
|
+
{#if dataLabelItems.length > 0}
|
|
833
|
+
<g class="st-barChart__dataLabels" aria-hidden="true">
|
|
834
|
+
{#each dataLabelItems as d (d.key)}
|
|
835
|
+
<text class="st-barChart__dataLabel" x={d.x} y={d.y} text-anchor={d.anchor} dominant-baseline={d.baseline}>{d.text}</text>
|
|
836
|
+
{/each}
|
|
837
|
+
</g>
|
|
838
|
+
{/if}
|
|
684
839
|
</svg>
|
|
685
840
|
</div>
|
|
686
841
|
|
|
@@ -934,4 +1089,39 @@
|
|
|
934
1089
|
.st-barChart__bar,
|
|
935
1090
|
.st-barChart__filterChip { transition: none; }
|
|
936
1091
|
}
|
|
1092
|
+
|
|
1093
|
+
/* --- Annotation layer ----------------------------------------------------
|
|
1094
|
+
Regions render BEHIND the bars; lines/shapes/points/labels render ABOVE. */
|
|
1095
|
+
.st-barChart__annotationRegion {
|
|
1096
|
+
fill: color-mix(in srgb, var(--st-semantic-feedback-info) 12%, transparent);
|
|
1097
|
+
stroke: none;
|
|
1098
|
+
}
|
|
1099
|
+
.st-barChart__annotationLine {
|
|
1100
|
+
stroke: var(--st-semantic-feedback-info);
|
|
1101
|
+
stroke-width: 1.5;
|
|
1102
|
+
stroke-dasharray: 4 3;
|
|
1103
|
+
}
|
|
1104
|
+
.st-barChart__annotationShape {
|
|
1105
|
+
fill: color-mix(in srgb, var(--st-semantic-feedback-info) 14%, transparent);
|
|
1106
|
+
stroke: var(--st-semantic-feedback-info);
|
|
1107
|
+
stroke-width: 1.5;
|
|
1108
|
+
}
|
|
1109
|
+
.st-barChart__annotationPoint {
|
|
1110
|
+
fill: var(--st-semantic-feedback-info);
|
|
1111
|
+
stroke: var(--st-semantic-surface-default);
|
|
1112
|
+
stroke-width: 1.5;
|
|
1113
|
+
}
|
|
1114
|
+
.st-barChart__annotationLabel,
|
|
1115
|
+
.st-barChart__annotationText {
|
|
1116
|
+
fill: var(--st-semantic-text-primary);
|
|
1117
|
+
font-size: 0.625rem;
|
|
1118
|
+
font-weight: 600;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/* Data labels — per-bar value, drawn on top. Token-only colour. */
|
|
1122
|
+
.st-barChart__dataLabel {
|
|
1123
|
+
fill: var(--st-semantic-text-primary);
|
|
1124
|
+
font-size: 0.6875rem;
|
|
1125
|
+
font-weight: 600;
|
|
1126
|
+
}
|
|
937
1127
|
</style>
|
|
@@ -31,6 +31,8 @@ export type ChartGoalLine = {
|
|
|
31
31
|
};
|
|
32
32
|
/** Value-axis scale type. `log` requires strictly positive values. */
|
|
33
33
|
export type ChartScale = "linear" | "log";
|
|
34
|
+
import { type ChartAnnotation } from "./chartAnnotations.js";
|
|
35
|
+
import { type DataLabelsProp } from "./chartDataLabels.js";
|
|
34
36
|
type BarChartProps = {
|
|
35
37
|
data: BarChartDatum[];
|
|
36
38
|
width?: number;
|
|
@@ -66,6 +68,22 @@ type BarChartProps = {
|
|
|
66
68
|
bands?: ChartBand[];
|
|
67
69
|
/** A single goal line, emphasised above the bars. */
|
|
68
70
|
goalLine?: ChartGoalLine;
|
|
71
|
+
/**
|
|
72
|
+
* Annotation overlay in DATA space. The x coordinate is CATEGORICAL — it
|
|
73
|
+
* matches a bar by its `label` (centre of band) and is ignored otherwise; the
|
|
74
|
+
* y coordinate (and `value`/`from`/`to`) are value-axis numbers. Regions
|
|
75
|
+
* render behind the bars, every other kind above. Additive: absent ⇒
|
|
76
|
+
* unchanged.
|
|
77
|
+
*/
|
|
78
|
+
annotations?: ChartAnnotation[];
|
|
79
|
+
/**
|
|
80
|
+
* Per-bar value labels. `false`/absent (default) → none. `true` → each bar's
|
|
81
|
+
* value with the chart's numeric formatter. Object → `format(value)` and/or
|
|
82
|
+
* a `position` override. Default position is `outside` (above the bar in
|
|
83
|
+
* vertical mode, past the bar end in horizontal mode). Labels are
|
|
84
|
+
* `aria-hidden` — the values already live in the accessible ChartDataList.
|
|
85
|
+
*/
|
|
86
|
+
dataLabels?: DataLabelsProp;
|
|
69
87
|
/**
|
|
70
88
|
* Value-axis scale. `"linear"` (default) is unchanged. `"log"` switches the
|
|
71
89
|
* value axis to a base-10 logarithmic scale — values `<= 0` are ignored for
|
|
@@ -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;
|
|
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;AAG/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,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAipBJ,QAAA,MAAM,QAAQ,mDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
package/dist/DonutChart.svelte
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
<script lang="ts">
|
|
14
14
|
import ChartDataList from "./ChartDataList.svelte";
|
|
15
|
+
import { formatDataLabel, normalizeDataLabels, type DataLabelsProp } from "./chartDataLabels.js";
|
|
15
16
|
|
|
16
17
|
type DonutChartProps = {
|
|
17
18
|
data: DonutChartDatum[];
|
|
@@ -21,6 +22,14 @@
|
|
|
21
22
|
thickness?: number;
|
|
22
23
|
/** Texte au centre (sinon le total). null pour masquer. */
|
|
23
24
|
centerLabel?: string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Per-slice value labels. `false`/absent (default) → none. `true` → each
|
|
27
|
+
* slice's value with the default formatter. Object → `format(value)` and/or a
|
|
28
|
+
* `position` override (default `center` of the arc). Slices too thin to fit a
|
|
29
|
+
* legible label are skipped. Labels are `aria-hidden` — the values already
|
|
30
|
+
* live in the accessible ChartDataList.
|
|
31
|
+
*/
|
|
32
|
+
dataLabels?: DataLabelsProp;
|
|
24
33
|
label: string;
|
|
25
34
|
class?: string;
|
|
26
35
|
};
|
|
@@ -30,6 +39,7 @@
|
|
|
30
39
|
size = 220,
|
|
31
40
|
thickness = 34,
|
|
32
41
|
centerLabel,
|
|
42
|
+
dataLabels,
|
|
33
43
|
label,
|
|
34
44
|
class: className
|
|
35
45
|
}: DonutChartProps = $props();
|
|
@@ -39,11 +49,14 @@
|
|
|
39
49
|
"category5", "category6", "category7", "category8"
|
|
40
50
|
];
|
|
41
51
|
|
|
52
|
+
// A slice must span at least this many degrees to host a legible label.
|
|
53
|
+
const DATA_LABEL_MIN_DEG = 18;
|
|
54
|
+
|
|
42
55
|
let hoveredIndex: number | null = $state(null);
|
|
43
56
|
|
|
44
57
|
const slices = $derived.by(() => {
|
|
45
58
|
const total = data.reduce((sum, d) => sum + Math.max(d.value, 0), 0);
|
|
46
|
-
if (total <= 0) return { total: 0, items: [] as Array<{ d: DonutChartDatum; path: string; tone: DonutChartTone; pct: number }> };
|
|
59
|
+
if (total <= 0) return { total: 0, items: [] as Array<{ d: DonutChartDatum; path: string; tone: DonutChartTone; pct: number; spanDeg: number; labelX: number; labelY: number }> };
|
|
47
60
|
const cx = size / 2;
|
|
48
61
|
const cy = size / 2;
|
|
49
62
|
const rOuter = size / 2 - 2;
|
|
@@ -64,7 +77,11 @@
|
|
|
64
77
|
const [x1i, y1i] = polar(rInner, a1);
|
|
65
78
|
const [x0i, y0i] = polar(rInner, a0);
|
|
66
79
|
const path = `M ${x0o} ${y0o} A ${rOuter} ${rOuter} 0 ${large} 1 ${x1o} ${y1o} L ${x1i} ${y1i} A ${rInner} ${rInner} 0 ${large} 0 ${x0i} ${y0i} Z`;
|
|
67
|
-
|
|
80
|
+
// Label anchor: centre of the arc (mid-angle, mid-radius of the ring).
|
|
81
|
+
const aMid = (a0 + a1) / 2;
|
|
82
|
+
const rMid = (rOuter + rInner) / 2;
|
|
83
|
+
const [labelX, labelY] = polar(rMid, aMid);
|
|
84
|
+
return { d, path, tone: d.tone ?? TONES[i % TONES.length], pct: frac * 100, spanDeg: (span * 180) / Math.PI, labelX, labelY };
|
|
68
85
|
});
|
|
69
86
|
return { total, items };
|
|
70
87
|
});
|
|
@@ -75,6 +92,24 @@
|
|
|
75
92
|
slices.items.map((slice) => `${slice.d.label}: ${slice.d.value} (${fmtPct(slice.pct)})`)
|
|
76
93
|
);
|
|
77
94
|
|
|
95
|
+
// --- Data labels ----------------------------------------------------------
|
|
96
|
+
// One value label centred in each arc (default `center`). Slices thinner than
|
|
97
|
+
// DATA_LABEL_MIN_DEG are skipped so labels stay legible. aria-hidden (values
|
|
98
|
+
// are in the ChartDataList already).
|
|
99
|
+
const dataLabelOpts = $derived(normalizeDataLabels(dataLabels));
|
|
100
|
+
const dataLabelItems = $derived(
|
|
101
|
+
dataLabelOpts.enabled
|
|
102
|
+
? slices.items
|
|
103
|
+
.filter((slice) => slice.spanDeg >= DATA_LABEL_MIN_DEG)
|
|
104
|
+
.map((slice) => ({
|
|
105
|
+
key: slice.d.label,
|
|
106
|
+
x: slice.labelX,
|
|
107
|
+
y: slice.labelY,
|
|
108
|
+
text: formatDataLabel(slice.d.value, dataLabelOpts, (v) => String(v))
|
|
109
|
+
}))
|
|
110
|
+
: []
|
|
111
|
+
);
|
|
112
|
+
|
|
78
113
|
function handleVisualPointerMove(event: PointerEvent) {
|
|
79
114
|
const target = event.target;
|
|
80
115
|
if (!(target instanceof Element)) {
|
|
@@ -109,6 +144,14 @@
|
|
|
109
144
|
{centerLabel ?? slices.total}
|
|
110
145
|
</text>
|
|
111
146
|
{/if}
|
|
147
|
+
<!-- Data labels — one value per slice, centred in the arc. aria-hidden. -->
|
|
148
|
+
{#if dataLabelItems.length > 0}
|
|
149
|
+
<g class="st-donutChart__dataLabels" aria-hidden="true">
|
|
150
|
+
{#each dataLabelItems as d (d.key)}
|
|
151
|
+
<text class="st-donutChart__dataLabel" x={d.x} y={d.y} text-anchor="middle" dominant-baseline="central">{d.text}</text>
|
|
152
|
+
{/each}
|
|
153
|
+
</g>
|
|
154
|
+
{/if}
|
|
112
155
|
{/if}
|
|
113
156
|
</svg>
|
|
114
157
|
</div>
|
|
@@ -160,6 +203,13 @@
|
|
|
160
203
|
font-weight: 650;
|
|
161
204
|
}
|
|
162
205
|
|
|
206
|
+
/* Data labels — per-slice value, centred in the arc. Token-only colour. */
|
|
207
|
+
.st-donutChart__dataLabel {
|
|
208
|
+
fill: var(--st-semantic-text-inverse);
|
|
209
|
+
font-size: 0.6875rem;
|
|
210
|
+
font-weight: 600;
|
|
211
|
+
}
|
|
212
|
+
|
|
163
213
|
.st-donutChart__tooltip {
|
|
164
214
|
background: var(--st-semantic-surface-inverse);
|
|
165
215
|
border-radius: var(--st-radius-sm, 0.25rem);
|
|
@@ -4,6 +4,7 @@ export type DonutChartDatum = {
|
|
|
4
4
|
value: number;
|
|
5
5
|
tone?: DonutChartTone;
|
|
6
6
|
};
|
|
7
|
+
import { type DataLabelsProp } from "./chartDataLabels.js";
|
|
7
8
|
type DonutChartProps = {
|
|
8
9
|
data: DonutChartDatum[];
|
|
9
10
|
/** Diamètre du SVG. */
|
|
@@ -12,6 +13,14 @@ type DonutChartProps = {
|
|
|
12
13
|
thickness?: number;
|
|
13
14
|
/** Texte au centre (sinon le total). null pour masquer. */
|
|
14
15
|
centerLabel?: string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Per-slice value labels. `false`/absent (default) → none. `true` → each
|
|
18
|
+
* slice's value with the default formatter. Object → `format(value)` and/or a
|
|
19
|
+
* `position` override (default `center` of the arc). Slices too thin to fit a
|
|
20
|
+
* legible label are skipped. Labels are `aria-hidden` — the values already
|
|
21
|
+
* live in the accessible ChartDataList.
|
|
22
|
+
*/
|
|
23
|
+
dataLabels?: DataLabelsProp;
|
|
15
24
|
label: string;
|
|
16
25
|
class?: string;
|
|
17
26
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DonutChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/DonutChart.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,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;
|
|
1
|
+
{"version":3,"file":"DonutChart.svelte.d.ts","sourceRoot":"","sources":["../src/lib/DonutChart.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,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AAIJ,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG/F,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,uBAAuB;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAsIJ,QAAA,MAAM,UAAU,qDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|