@internetstiftelsen/charts 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -8
  3. package/{area.d.ts → dist/area.d.ts} +1 -2
  4. package/{area.js → dist/area.js} +2 -19
  5. package/{bar.d.ts → dist/bar.d.ts} +3 -5
  6. package/{bar.js → dist/bar.js} +8 -33
  7. package/{base-chart.d.ts → dist/base-chart.d.ts} +75 -14
  8. package/{base-chart.js → dist/base-chart.js} +429 -122
  9. package/dist/chart-interface.d.ts +19 -0
  10. package/{donut-center-content.d.ts → dist/donut-center-content.d.ts} +1 -1
  11. package/dist/donut-chart.d.ts +51 -0
  12. package/dist/donut-chart.js +374 -0
  13. package/{gauge-chart.d.ts → dist/gauge-chart.d.ts} +19 -8
  14. package/{gauge-chart.js → dist/gauge-chart.js} +317 -106
  15. package/{grid.d.ts → dist/grid.d.ts} +1 -1
  16. package/{layout-manager.d.ts → dist/layout-manager.d.ts} +5 -5
  17. package/{legend.d.ts → dist/legend.d.ts} +3 -1
  18. package/{legend.js → dist/legend.js} +32 -0
  19. package/{line.d.ts → dist/line.d.ts} +1 -1
  20. package/{line.js → dist/line.js} +3 -25
  21. package/{pie-chart.d.ts → dist/pie-chart.d.ts} +10 -21
  22. package/{pie-chart.js → dist/pie-chart.js} +51 -172
  23. package/dist/radial-chart-base.d.ts +25 -0
  24. package/dist/radial-chart-base.js +79 -0
  25. package/dist/scale-utils.d.ts +3 -0
  26. package/dist/scale-utils.js +14 -0
  27. package/{theme.d.ts → dist/theme.d.ts} +2 -0
  28. package/{theme.js → dist/theme.js} +24 -29
  29. package/{title.d.ts → dist/title.d.ts} +1 -1
  30. package/{tooltip.d.ts → dist/tooltip.d.ts} +1 -1
  31. package/{tooltip.js → dist/tooltip.js} +239 -74
  32. package/{types.d.ts → dist/types.d.ts} +27 -10
  33. package/{utils.d.ts → dist/utils.d.ts} +7 -2
  34. package/{utils.js → dist/utils.js} +24 -5
  35. package/dist/word-cloud-chart.d.ts +32 -0
  36. package/dist/word-cloud-chart.js +201 -0
  37. package/{x-axis.d.ts → dist/x-axis.d.ts} +2 -1
  38. package/{x-axis.js → dist/x-axis.js} +18 -14
  39. package/{xy-chart.d.ts → dist/xy-chart.d.ts} +14 -9
  40. package/{xy-chart.js → dist/xy-chart.js} +107 -130
  41. package/{y-axis.d.ts → dist/y-axis.d.ts} +1 -1
  42. package/{y-axis.js → dist/y-axis.js} +4 -4
  43. package/package.json +39 -35
  44. package/chart-interface.d.ts +0 -13
  45. package/donut-chart.d.ts +0 -38
  46. package/donut-chart.js +0 -316
  47. /package/{chart-interface.js → dist/chart-interface.js} +0 -0
  48. /package/{donut-center-content.js → dist/donut-center-content.js} +0 -0
  49. /package/{export-image.d.ts → dist/export-image.d.ts} +0 -0
  50. /package/{export-image.js → dist/export-image.js} +0 -0
  51. /package/{export-pdf.d.ts → dist/export-pdf.d.ts} +0 -0
  52. /package/{export-pdf.js → dist/export-pdf.js} +0 -0
  53. /package/{export-tabular.d.ts → dist/export-tabular.d.ts} +0 -0
  54. /package/{export-tabular.js → dist/export-tabular.js} +0 -0
  55. /package/{export-xlsx.d.ts → dist/export-xlsx.d.ts} +0 -0
  56. /package/{export-xlsx.js → dist/export-xlsx.js} +0 -0
  57. /package/{grid.js → dist/grid.js} +0 -0
  58. /package/{grouped-data.d.ts → dist/grouped-data.d.ts} +0 -0
  59. /package/{grouped-data.js → dist/grouped-data.js} +0 -0
  60. /package/{grouped-tabular.d.ts → dist/grouped-tabular.d.ts} +0 -0
  61. /package/{grouped-tabular.js → dist/grouped-tabular.js} +0 -0
  62. /package/{layout-manager.js → dist/layout-manager.js} +0 -0
  63. /package/{title.js → dist/title.js} +0 -0
  64. /package/{types.js → dist/types.js} +0 -0
  65. /package/{validation.d.ts → dist/validation.d.ts} +0 -0
  66. /package/{validation.js → dist/validation.js} +0 -0
@@ -0,0 +1,19 @@
1
+ import type { ExportHooks } from './types.js';
2
+ export type ChartComponentType = 'line' | 'area' | 'bar' | 'xAxis' | 'yAxis' | 'grid' | 'tooltip' | 'legend' | 'title' | 'donutCenterContent';
3
+ export interface ChartComponentBase {
4
+ type: ChartComponentType;
5
+ }
6
+ export interface ChartComponent<TConfig = unknown> extends ChartComponentBase {
7
+ type: ChartComponentType;
8
+ exportHooks?: ExportHooks<TConfig>;
9
+ }
10
+ export type ComponentSpace = {
11
+ width: number;
12
+ height: number;
13
+ position: 'top' | 'bottom' | 'left' | 'right';
14
+ };
15
+ export interface LayoutAwareComponentBase extends ChartComponentBase {
16
+ getRequiredSpace(): ComponentSpace;
17
+ }
18
+ export interface LayoutAwareComponent<TConfig = unknown> extends ChartComponent<TConfig>, LayoutAwareComponentBase {
19
+ }
@@ -27,7 +27,7 @@ export declare class DonutCenterContent implements ChartComponent<DonutCenterCon
27
27
  private readonly config;
28
28
  constructor(config?: DonutCenterContentConfig);
29
29
  getExportConfig(): DonutCenterContentConfigBase;
30
- createExportComponent(override?: Partial<DonutCenterContentConfigBase>): ChartComponent;
30
+ createExportComponent(override?: Partial<DonutCenterContentConfigBase>): ChartComponent<DonutCenterContentConfigBase>;
31
31
  render(svg: Selection<SVGSVGElement, undefined, null, undefined>, cx: number, cy: number, theme: ChartTheme, fontScale?: number): void;
32
32
  }
33
33
  export {};
@@ -0,0 +1,51 @@
1
+ import type { DataItem, LegendSeries } from './types.js';
2
+ import { type BaseChartConfig, type BaseRenderContext } from './base-chart.js';
3
+ import type { ChartComponentBase } from './chart-interface.js';
4
+ import { RadialChartBase } from './radial-chart-base.js';
5
+ export type DonutConfig = {
6
+ innerRadius?: number;
7
+ padAngle?: number;
8
+ cornerRadius?: number;
9
+ };
10
+ export type DonutValueLabelPosition = 'outside' | 'auto';
11
+ export type DonutValueLabelConfig = {
12
+ show?: boolean;
13
+ position?: DonutValueLabelPosition;
14
+ outsideOffset?: number;
15
+ minVerticalSpacing?: number;
16
+ formatter?: (label: string, value: number, data: DataItem, percentage: number) => string;
17
+ };
18
+ export type DonutChartConfig = BaseChartConfig & {
19
+ donut?: DonutConfig;
20
+ valueLabel?: DonutValueLabelConfig;
21
+ valueKey?: string;
22
+ labelKey?: string;
23
+ };
24
+ export declare class DonutChart extends RadialChartBase {
25
+ private readonly innerRadiusRatio;
26
+ private readonly padAngle;
27
+ private readonly cornerRadius;
28
+ private readonly valueKey;
29
+ private readonly labelKey;
30
+ private readonly valueLabel;
31
+ private segments;
32
+ private centerContent;
33
+ constructor(config: DonutChartConfig);
34
+ private validateDonutData;
35
+ private prepareSegments;
36
+ addChild(component: ChartComponentBase): this;
37
+ protected getExportComponents(): ChartComponentBase[];
38
+ update(data: DataItem[]): void;
39
+ protected createExportChart(): RadialChartBase;
40
+ protected applyComponentOverrides(overrides: Map<ChartComponentBase, ChartComponentBase>): () => void;
41
+ protected syncDerivedState(): void;
42
+ protected renderChart({ svg, plotGroup, plotArea, }: BaseRenderContext): void;
43
+ protected getLegendSeries(): LegendSeries[];
44
+ private buildTooltipContent;
45
+ private formatValueLabelText;
46
+ private renderSegments;
47
+ private renderLabels;
48
+ private getArcPoint;
49
+ private resolveOutsideLabel;
50
+ private adjustOutsideLabelPositions;
51
+ }
@@ -0,0 +1,374 @@
1
+ import { arc, pie, select } from 'd3';
2
+ import { sanitizeForCSS } from './utils.js';
3
+ import { ChartValidator } from './validation.js';
4
+ import { RadialChartBase } from './radial-chart-base.js';
5
+ const HOVER_EXPAND_PX = 8;
6
+ const ANIMATION_DURATION_MS = 150;
7
+ const OUTSIDE_LABEL_TEXT_OFFSET_PX = 10;
8
+ const OUTSIDE_LABEL_LINE_INSET_PX = 4;
9
+ export class DonutChart extends RadialChartBase {
10
+ constructor(config) {
11
+ super(config);
12
+ Object.defineProperty(this, "innerRadiusRatio", {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value: void 0
17
+ });
18
+ Object.defineProperty(this, "padAngle", {
19
+ enumerable: true,
20
+ configurable: true,
21
+ writable: true,
22
+ value: void 0
23
+ });
24
+ Object.defineProperty(this, "cornerRadius", {
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ value: void 0
29
+ });
30
+ Object.defineProperty(this, "valueKey", {
31
+ enumerable: true,
32
+ configurable: true,
33
+ writable: true,
34
+ value: void 0
35
+ });
36
+ Object.defineProperty(this, "labelKey", {
37
+ enumerable: true,
38
+ configurable: true,
39
+ writable: true,
40
+ value: void 0
41
+ });
42
+ Object.defineProperty(this, "valueLabel", {
43
+ enumerable: true,
44
+ configurable: true,
45
+ writable: true,
46
+ value: void 0
47
+ });
48
+ Object.defineProperty(this, "segments", {
49
+ enumerable: true,
50
+ configurable: true,
51
+ writable: true,
52
+ value: []
53
+ });
54
+ Object.defineProperty(this, "centerContent", {
55
+ enumerable: true,
56
+ configurable: true,
57
+ writable: true,
58
+ value: null
59
+ });
60
+ const donut = config.donut ?? {};
61
+ this.innerRadiusRatio =
62
+ donut.innerRadius ?? this.theme.donut.innerRadius;
63
+ this.padAngle = donut.padAngle ?? this.theme.donut.padAngle;
64
+ this.cornerRadius = donut.cornerRadius ?? this.theme.donut.cornerRadius;
65
+ this.valueKey = config.valueKey ?? 'value';
66
+ this.labelKey = config.labelKey ?? 'name';
67
+ this.valueLabel = {
68
+ show: config.valueLabel?.show ?? false,
69
+ position: config.valueLabel?.position ?? 'auto',
70
+ outsideOffset: config.valueLabel?.outsideOffset ?? 16,
71
+ minVerticalSpacing: config.valueLabel?.minVerticalSpacing ?? 14,
72
+ formatter: config.valueLabel?.formatter ??
73
+ ((label, value, _data, _percentage) => `${label}: ${value}`),
74
+ };
75
+ this.initializeDataState();
76
+ }
77
+ validateDonutData() {
78
+ ChartValidator.validateDataKey(this.data, this.labelKey, 'DonutChart');
79
+ ChartValidator.validateDataKey(this.data, this.valueKey, 'DonutChart');
80
+ ChartValidator.validateNumericData(this.data, this.valueKey, 'DonutChart');
81
+ for (const [index, item] of this.data.entries()) {
82
+ const value = this.parseValue(item[this.valueKey]);
83
+ if (value < 0) {
84
+ throw new Error(`DonutChart: data item at index ${index} has negative value '${item[this.valueKey]}' for key '${this.valueKey}'`);
85
+ }
86
+ }
87
+ if (this.valueLabel.outsideOffset < 0) {
88
+ throw new Error(`DonutChart: valueLabel.outsideOffset must be >= 0, received '${this.valueLabel.outsideOffset}'`);
89
+ }
90
+ if (this.valueLabel.minVerticalSpacing < 0) {
91
+ throw new Error(`DonutChart: valueLabel.minVerticalSpacing must be >= 0, received '${this.valueLabel.minVerticalSpacing}'`);
92
+ }
93
+ }
94
+ prepareSegments() {
95
+ this.segments = this.data.map((item, index) => ({
96
+ label: String(item[this.labelKey]),
97
+ value: this.parseValue(item[this.valueKey]),
98
+ color: (typeof item['color'] === 'string' ? item['color'] : null) ||
99
+ this.theme.colorPalette[index % this.theme.colorPalette.length],
100
+ source: item,
101
+ }));
102
+ }
103
+ addChild(component) {
104
+ if (this.tryRegisterComponent(component, [
105
+ {
106
+ type: 'donutCenterContent',
107
+ get: () => this.centerContent,
108
+ set: (entry) => {
109
+ this.centerContent = entry;
110
+ },
111
+ },
112
+ ])) {
113
+ return this;
114
+ }
115
+ return super.addChild(component);
116
+ }
117
+ getExportComponents() {
118
+ return [
119
+ ...this.getBaseExportComponents({
120
+ title: true,
121
+ }),
122
+ ...(this.centerContent ? [this.centerContent] : []),
123
+ ...this.getBaseExportComponents({
124
+ tooltip: true,
125
+ legend: this.legend?.isInlineMode(),
126
+ }),
127
+ ];
128
+ }
129
+ update(data) {
130
+ super.update(data);
131
+ }
132
+ createExportChart() {
133
+ return new DonutChart({
134
+ data: this.data,
135
+ width: this.configuredWidth,
136
+ height: this.configuredHeight,
137
+ theme: this.theme,
138
+ responsive: this.responsiveConfig,
139
+ donut: {
140
+ innerRadius: this.innerRadiusRatio,
141
+ padAngle: this.padAngle,
142
+ cornerRadius: this.cornerRadius,
143
+ },
144
+ valueLabel: this.valueLabel,
145
+ valueKey: this.valueKey,
146
+ labelKey: this.labelKey,
147
+ });
148
+ }
149
+ applyComponentOverrides(overrides) {
150
+ const restoreBase = super.applyComponentOverrides(overrides);
151
+ const restoreCenterContent = this.applySlotOverrides(overrides, [
152
+ {
153
+ type: 'donutCenterContent',
154
+ get: () => this.centerContent,
155
+ set: (component) => {
156
+ this.centerContent = component;
157
+ },
158
+ },
159
+ ]);
160
+ return () => {
161
+ restoreCenterContent();
162
+ restoreBase();
163
+ };
164
+ }
165
+ syncDerivedState() {
166
+ this.validateDonutData();
167
+ this.prepareSegments();
168
+ }
169
+ renderChart({ svg, plotGroup, plotArea, }) {
170
+ this.renderTitle(svg);
171
+ const visibleSegments = this.getVisibleRadialItems(this.segments);
172
+ const { cx, cy, outerRadius, innerRadius, fontScale } = this.getRadialLayout(plotArea, this.innerRadiusRatio);
173
+ this.initializeTooltip();
174
+ const { segmentGroup, pieData } = this.renderSegments(plotGroup, visibleSegments, cx, cy, innerRadius, outerRadius);
175
+ if (this.valueLabel.show && visibleSegments.length > 0) {
176
+ this.renderLabels(segmentGroup, pieData, outerRadius, visibleSegments.reduce((sum, segment) => {
177
+ return sum + segment.value;
178
+ }, 0), fontScale);
179
+ }
180
+ if (this.centerContent) {
181
+ this.centerContent.render(svg, cx, cy, this.renderTheme, fontScale);
182
+ }
183
+ this.renderInlineLegend(svg);
184
+ }
185
+ getLegendSeries() {
186
+ return this.getRadialLegendSeries(this.segments);
187
+ }
188
+ buildTooltipContent(d, segments) {
189
+ const total = segments.reduce((sum, s) => sum + s.value, 0);
190
+ const percentage = total > 0 ? ((d.data.value / total) * 100).toFixed(1) : '0.0';
191
+ const dataItem = this.data.find((item) => String(item[this.labelKey]) === d.data.label);
192
+ if (this.tooltip?.customFormatter) {
193
+ return this.tooltip.customFormatter(dataItem || {}, [
194
+ { dataKey: d.data.label, fill: d.data.color },
195
+ ]);
196
+ }
197
+ if (this.tooltip?.formatter) {
198
+ return `<strong>${d.data.label}</strong><br/>${this.tooltip.formatter(d.data.label, d.data.value, dataItem || {})}`;
199
+ }
200
+ return `<strong>${d.data.label}</strong><br/>${d.data.value} (${percentage}%)`;
201
+ }
202
+ formatValueLabelText(segment, total) {
203
+ return this.valueLabel.formatter(segment.label, segment.value, segment.source, total > 0 ? (segment.value / total) * 100 : 0);
204
+ }
205
+ renderSegments(plotGroup, segments, cx, cy, innerRadius, outerRadius) {
206
+ const pieGenerator = pie()
207
+ .value((d) => d.value)
208
+ .padAngle(this.padAngle)
209
+ .sort(null);
210
+ const arcGenerator = arc()
211
+ .innerRadius(innerRadius)
212
+ .outerRadius(outerRadius)
213
+ .cornerRadius(this.cornerRadius);
214
+ const hoverArcGenerator = arc()
215
+ .innerRadius(innerRadius)
216
+ .outerRadius(outerRadius + HOVER_EXPAND_PX)
217
+ .cornerRadius(this.cornerRadius);
218
+ const pieData = pieGenerator(segments);
219
+ const segmentGroup = plotGroup
220
+ .append('g')
221
+ .attr('class', 'donut-segments')
222
+ .attr('transform', `translate(${cx}, ${cy})`);
223
+ const resolveTooltipDiv = () => this.tooltip
224
+ ? select(`#${this.tooltip.id}`)
225
+ : null;
226
+ segmentGroup
227
+ .selectAll('.donut-segment')
228
+ .data(pieData)
229
+ .join('path')
230
+ .attr('class', (d) => `donut-segment segment-${sanitizeForCSS(d.data.label)}`)
231
+ .attr('d', arcGenerator)
232
+ .attr('fill', (d) => d.data.color)
233
+ .style('cursor', 'pointer')
234
+ .style('transition', 'opacity 0.15s ease')
235
+ .on('mouseenter', (event, d) => {
236
+ select(event.currentTarget)
237
+ .transition()
238
+ .duration(ANIMATION_DURATION_MS)
239
+ .attr('d', hoverArcGenerator(d));
240
+ segmentGroup
241
+ .selectAll('.donut-segment')
242
+ .filter((_, i, nodes) => nodes[i] !== event.currentTarget)
243
+ .style('opacity', 0.5);
244
+ const tooltipDiv = resolveTooltipDiv();
245
+ if (tooltipDiv && !tooltipDiv.empty()) {
246
+ tooltipDiv
247
+ .style('visibility', 'visible')
248
+ .html(this.buildTooltipContent(d, segments));
249
+ this.positionTooltipFromPointer(event, tooltipDiv);
250
+ }
251
+ })
252
+ .on('mousemove', (event) => {
253
+ const tooltipDiv = resolveTooltipDiv();
254
+ if (tooltipDiv && !tooltipDiv.empty()) {
255
+ this.positionTooltipFromPointer(event, tooltipDiv);
256
+ }
257
+ })
258
+ .on('mouseleave', (event, d) => {
259
+ select(event.currentTarget)
260
+ .transition()
261
+ .duration(ANIMATION_DURATION_MS)
262
+ .attr('d', arcGenerator(d));
263
+ segmentGroup.selectAll('.donut-segment').style('opacity', 1);
264
+ const tooltipDiv = resolveTooltipDiv();
265
+ if (tooltipDiv && !tooltipDiv.empty()) {
266
+ tooltipDiv.style('visibility', 'hidden');
267
+ }
268
+ });
269
+ return {
270
+ segmentGroup,
271
+ pieData,
272
+ };
273
+ }
274
+ renderLabels(segmentGroup, pieData, outerRadius, total, fontScale) {
275
+ if (pieData.length === 0) {
276
+ return;
277
+ }
278
+ const labelGroup = segmentGroup
279
+ .append('g')
280
+ .attr('class', 'donut-labels');
281
+ const fontSize = this.renderTheme.valueLabel.fontSize * fontScale;
282
+ const fontFamily = this.renderTheme.valueLabel.fontFamily;
283
+ const fontWeight = this.renderTheme.valueLabel.fontWeight;
284
+ const outsideLabels = pieData.map((datum) => this.resolveOutsideLabel(datum, outerRadius));
285
+ const adjustedOutsideLabels = this.adjustOutsideLabelPositions(outsideLabels, outerRadius);
286
+ const linesGroup = labelGroup
287
+ .append('g')
288
+ .attr('class', 'donut-label-leader-lines');
289
+ adjustedOutsideLabels.forEach((outsideLabel) => {
290
+ const midAngle = (outsideLabel.datum.startAngle + outsideLabel.datum.endAngle) /
291
+ 2;
292
+ const anchorX = outerRadius + this.valueLabel.outsideOffset;
293
+ const textX = outsideLabel.side === 'right'
294
+ ? anchorX + OUTSIDE_LABEL_TEXT_OFFSET_PX
295
+ : -anchorX - OUTSIDE_LABEL_TEXT_OFFSET_PX;
296
+ const lineEndX = outsideLabel.side === 'right'
297
+ ? textX - OUTSIDE_LABEL_TEXT_OFFSET_PX / 2
298
+ : textX + OUTSIDE_LABEL_TEXT_OFFSET_PX / 2;
299
+ const start = this.getArcPoint(midAngle, outerRadius + OUTSIDE_LABEL_LINE_INSET_PX);
300
+ const elbow = this.getArcPoint(midAngle, outerRadius + this.valueLabel.outsideOffset * 0.65);
301
+ const elbowY = outsideLabel.y;
302
+ linesGroup
303
+ .append('path')
304
+ .attr('class', `donut-label-line donut-label-line-${sanitizeForCSS(outsideLabel.datum.data.label)}`)
305
+ .attr('d', `M ${start.x},${start.y} L ${elbow.x},${elbowY} L ${lineEndX},${outsideLabel.y}`)
306
+ .attr('fill', 'none')
307
+ .attr('stroke', this.renderTheme.valueLabel.border)
308
+ .attr('stroke-width', 1);
309
+ labelGroup
310
+ .append('text')
311
+ .attr('class', `donut-label donut-label--outside donut-label-${sanitizeForCSS(outsideLabel.datum.data.label)}`)
312
+ .attr('x', textX)
313
+ .attr('y', outsideLabel.y)
314
+ .attr('text-anchor', outsideLabel.textAnchor)
315
+ .attr('dominant-baseline', 'middle')
316
+ .attr('font-family', fontFamily)
317
+ .attr('font-size', `${fontSize}px`)
318
+ .attr('font-weight', fontWeight)
319
+ .attr('fill', this.renderTheme.valueLabel.color)
320
+ .text(this.formatValueLabelText(outsideLabel.datum.data, total));
321
+ });
322
+ }
323
+ getArcPoint(angle, radius) {
324
+ return {
325
+ x: Math.sin(angle) * radius,
326
+ y: -Math.cos(angle) * radius,
327
+ };
328
+ }
329
+ resolveOutsideLabel(datum, outerRadius) {
330
+ const midAngle = (datum.startAngle + datum.endAngle) / 2;
331
+ const point = this.getArcPoint(midAngle, outerRadius + this.valueLabel.outsideOffset);
332
+ const side = point.x >= 0 ? 'right' : 'left';
333
+ return {
334
+ datum,
335
+ y: point.y,
336
+ side,
337
+ textAnchor: side === 'right' ? 'start' : 'end',
338
+ };
339
+ }
340
+ adjustOutsideLabelPositions(labels, outerRadius) {
341
+ const adjustForSide = (side) => {
342
+ const sideLabels = labels
343
+ .filter((label) => label.side === side)
344
+ .sort((a, b) => a.y - b.y);
345
+ if (sideLabels.length === 0) {
346
+ return [];
347
+ }
348
+ const topLimit = -outerRadius;
349
+ const bottomLimit = outerRadius;
350
+ sideLabels[0].y = Math.max(topLimit, sideLabels[0].y);
351
+ for (let i = 1; i < sideLabels.length; i++) {
352
+ const minY = sideLabels[i - 1].y + this.valueLabel.minVerticalSpacing;
353
+ sideLabels[i].y = Math.max(sideLabels[i].y, minY);
354
+ }
355
+ const overflow = sideLabels[sideLabels.length - 1].y - bottomLimit;
356
+ if (overflow > 0) {
357
+ sideLabels[sideLabels.length - 1].y -= overflow;
358
+ for (let i = sideLabels.length - 2; i >= 0; i--) {
359
+ const maxY = sideLabels[i + 1].y -
360
+ this.valueLabel.minVerticalSpacing;
361
+ sideLabels[i].y = Math.min(sideLabels[i].y, maxY);
362
+ }
363
+ const underflow = topLimit - sideLabels[0].y;
364
+ if (underflow > 0) {
365
+ sideLabels.forEach((label) => {
366
+ label.y += underflow;
367
+ });
368
+ }
369
+ }
370
+ return sideLabels;
371
+ };
372
+ return [...adjustForSide('left'), ...adjustForSide('right')];
373
+ }
374
+ }
@@ -1,6 +1,6 @@
1
1
  import type { DataItem, LegendSeries } from './types.js';
2
- import { BaseChart, type BaseChartConfig } from './base-chart.js';
3
- import type { ChartComponent, LayoutAwareComponent } from './chart-interface.js';
2
+ import { BaseChart, type BaseChartConfig, type BaseRenderContext } from './base-chart.js';
3
+ import type { ChartComponentBase } from './chart-interface.js';
4
4
  export type GaugeSegment = {
5
5
  from: number;
6
6
  to: number;
@@ -35,10 +35,17 @@ export type GaugeLabelStyle = {
35
35
  fontWeight?: number | string;
36
36
  color?: string;
37
37
  };
38
+ export type GaugeAnimationEasingPreset = 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'bounce-out' | 'elastic-out';
39
+ export type GaugeAnimationConfig = {
40
+ show?: boolean;
41
+ duration?: number;
42
+ easing?: GaugeAnimationEasingPreset | `linear(${string})` | ((progress: number) => number);
43
+ };
38
44
  export type GaugeConfig = {
39
45
  value?: number;
40
46
  min?: number;
41
47
  max?: number;
48
+ animate?: boolean | GaugeAnimationConfig;
42
49
  halfCircle?: boolean;
43
50
  startAngle?: number;
44
51
  endAngle?: number;
@@ -71,6 +78,7 @@ export declare class GaugeChart extends BaseChart {
71
78
  private readonly targetValueKey;
72
79
  private readonly minValue;
73
80
  private readonly maxValue;
81
+ private readonly animation;
74
82
  private readonly halfCircle;
75
83
  private readonly startAngle;
76
84
  private readonly endAngle;
@@ -91,11 +99,15 @@ export declare class GaugeChart extends BaseChart {
91
99
  private segments;
92
100
  private value;
93
101
  private targetValue;
102
+ private lastRenderedValue;
94
103
  constructor(config: GaugeChartConfig);
95
104
  private normalizeNeedleConfig;
96
105
  private normalizeMarkerConfig;
97
106
  private getThemePaletteColor;
98
107
  private normalizeTickConfig;
108
+ private normalizeAnimationConfig;
109
+ private resolveAnimationEasing;
110
+ private parseCssLinearEasing;
99
111
  private normalizeTickLabelStyle;
100
112
  private normalizeValueLabelStyle;
101
113
  private validateGaugeConfig;
@@ -108,13 +120,13 @@ export declare class GaugeChart extends BaseChart {
108
120
  private clampToDomain;
109
121
  private prepareSegments;
110
122
  private defaultFormat;
111
- addChild(component: ChartComponent): this;
112
- protected getExportComponents(): ChartComponent[];
123
+ protected getExportComponents(): ChartComponentBase[];
113
124
  update(data: DataItem[]): void;
114
- protected getLayoutComponents(): LayoutAwareComponent[];
115
- protected prepareLayout(): void;
116
125
  protected createExportChart(): BaseChart;
117
- protected renderChart(): void;
126
+ protected syncDerivedState(): void;
127
+ protected renderChart({ svg, plotGroup, plotArea, }: BaseRenderContext): void;
128
+ private resolveAnimationStartValue;
129
+ private shouldAnimateTransition;
118
130
  private buildAriaLabel;
119
131
  private findSegmentStatusLabel;
120
132
  private getVisibleSegments;
@@ -135,6 +147,5 @@ export declare class GaugeChart extends BaseChart {
135
147
  private resolveTooltipDiv;
136
148
  private buildTooltipContent;
137
149
  private positionTooltip;
138
- private renderLegend;
139
150
  protected getLegendSeries(): LegendSeries[];
140
151
  }