@internetstiftelsen/charts 0.10.0 → 0.11.0
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/README.md +65 -1
- package/dist/area.d.ts +11 -1
- package/dist/area.js +199 -55
- package/dist/bar.d.ts +26 -1
- package/dist/bar.js +425 -306
- package/dist/base-chart.d.ts +5 -0
- package/dist/base-chart.js +91 -67
- package/dist/chart-group.d.ts +16 -0
- package/dist/chart-group.js +201 -143
- package/dist/donut-center-content.d.ts +1 -0
- package/dist/donut-center-content.js +21 -38
- package/dist/donut-chart.js +32 -32
- package/dist/gauge-chart.d.ts +23 -4
- package/dist/gauge-chart.js +235 -185
- package/dist/lazy-mount.d.ts +13 -0
- package/dist/lazy-mount.js +90 -0
- package/dist/legend.js +10 -9
- package/dist/line.d.ts +9 -1
- package/dist/line.js +144 -24
- package/dist/pie-chart.d.ts +3 -0
- package/dist/pie-chart.js +49 -47
- package/dist/radial-chart-base.d.ts +4 -3
- package/dist/radial-chart-base.js +27 -12
- package/dist/scatter.d.ts +5 -1
- package/dist/scatter.js +92 -9
- package/dist/theme.js +17 -0
- package/dist/tooltip.d.ts +55 -3
- package/dist/tooltip.js +968 -159
- package/dist/types.d.ts +23 -1
- package/dist/utils.js +11 -19
- package/dist/x-axis.d.ts +10 -0
- package/dist/x-axis.js +190 -149
- package/dist/xy-animation.d.ts +3 -0
- package/dist/xy-animation.js +2 -0
- package/dist/xy-chart.d.ts +35 -1
- package/dist/xy-chart.js +358 -153
- package/dist/xy-motion/config.d.ts +2 -0
- package/dist/xy-motion/config.js +177 -0
- package/dist/xy-motion/driver.d.ts +9 -0
- package/dist/xy-motion/driver.js +10 -0
- package/dist/xy-motion/helpers.d.ts +17 -0
- package/dist/xy-motion/helpers.js +105 -0
- package/dist/xy-motion/live-state.d.ts +8 -0
- package/dist/xy-motion/live-state.js +240 -0
- package/dist/xy-motion/noop-xy-motion-driver.d.ts +9 -0
- package/dist/xy-motion/noop-xy-motion-driver.js +15 -0
- package/dist/xy-motion/types.d.ts +85 -0
- package/dist/xy-motion/types.js +1 -0
- package/dist/xy-motion/xy-motion-driver.d.ts +19 -0
- package/dist/xy-motion/xy-motion-driver.js +130 -0
- package/dist/y-axis.d.ts +7 -2
- package/dist/y-axis.js +99 -10
- package/docs/components.md +50 -1
- package/docs/getting-started.md +35 -0
- package/docs/theming.md +14 -0
- package/docs/xy-chart.md +88 -7
- package/package.json +5 -4
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { XYMotionDriverContract } from './driver.js';
|
|
2
|
+
import type { NormalizedXYAnimation, XYAreaAnimationContext, XYBarAnimationContext, XYMotionRenderResult, XYMotionSeries, XYMotionUpdateContext, XYPointAnimationContext } from './types.js';
|
|
3
|
+
export declare class XYMotionDriver implements XYMotionDriverContract {
|
|
4
|
+
private readonly animation;
|
|
5
|
+
private hasRenderedLive;
|
|
6
|
+
private nextRenderAnimationMode;
|
|
7
|
+
private pendingAnimationSnapshot;
|
|
8
|
+
private pendingPathState;
|
|
9
|
+
private lastSeriesSnapshot;
|
|
10
|
+
constructor(animation: NormalizedXYAnimation);
|
|
11
|
+
prepareForUpdate(context: XYMotionUpdateContext): void;
|
|
12
|
+
getPointAnimationContext(series: XYMotionSeries, baselineY: number): XYPointAnimationContext | undefined;
|
|
13
|
+
getAreaAnimationContext(series: XYMotionSeries): XYAreaAnimationContext | undefined;
|
|
14
|
+
getBarAnimationContext(series: XYMotionSeries, baselineValuePosition: number): XYBarAnimationContext | undefined;
|
|
15
|
+
completeRender(result: XYMotionRenderResult): Promise<void>;
|
|
16
|
+
private getRenderMode;
|
|
17
|
+
private getPreviousSeriesSnapshot;
|
|
18
|
+
private getPreviousPathState;
|
|
19
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { createXYSeriesSnapshotId } from './helpers.js';
|
|
2
|
+
import { captureLiveAnimationState } from './live-state.js';
|
|
3
|
+
export class XYMotionDriver {
|
|
4
|
+
constructor(animation) {
|
|
5
|
+
Object.defineProperty(this, "animation", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
writable: true,
|
|
9
|
+
value: void 0
|
|
10
|
+
});
|
|
11
|
+
Object.defineProperty(this, "hasRenderedLive", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: false
|
|
16
|
+
});
|
|
17
|
+
Object.defineProperty(this, "nextRenderAnimationMode", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true,
|
|
21
|
+
value: void 0
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(this, "pendingAnimationSnapshot", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
writable: true,
|
|
27
|
+
value: null
|
|
28
|
+
});
|
|
29
|
+
Object.defineProperty(this, "pendingPathState", {
|
|
30
|
+
enumerable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true,
|
|
33
|
+
value: new Map()
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(this, "lastSeriesSnapshot", {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
writable: true,
|
|
39
|
+
value: new Map()
|
|
40
|
+
});
|
|
41
|
+
this.animation = animation;
|
|
42
|
+
this.nextRenderAnimationMode = 'initial';
|
|
43
|
+
}
|
|
44
|
+
prepareForUpdate(context) {
|
|
45
|
+
if (!this.hasRenderedLive) {
|
|
46
|
+
this.pendingAnimationSnapshot = null;
|
|
47
|
+
this.pendingPathState = new Map();
|
|
48
|
+
this.nextRenderAnimationMode = 'none';
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const liveState = captureLiveAnimationState(context.plotGroup, context.visibleSeries, this.lastSeriesSnapshot);
|
|
52
|
+
this.pendingAnimationSnapshot = liveState.snapshotCollection;
|
|
53
|
+
this.pendingPathState = liveState.pathState;
|
|
54
|
+
this.nextRenderAnimationMode =
|
|
55
|
+
this.animation.duration > 0 ? 'update' : 'none';
|
|
56
|
+
}
|
|
57
|
+
getPointAnimationContext(series, baselineY) {
|
|
58
|
+
const mode = this.getRenderMode();
|
|
59
|
+
if (mode === 'none') {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
mode,
|
|
64
|
+
duration: this.animation.duration,
|
|
65
|
+
easing: this.animation.easing,
|
|
66
|
+
baselineY,
|
|
67
|
+
previousSnapshot: this.getPreviousSeriesSnapshot(series.type, series.dataKey),
|
|
68
|
+
previousPath: series.type === 'line'
|
|
69
|
+
? this.getPreviousPathState(series.type, series.dataKey)
|
|
70
|
+
?.linePath
|
|
71
|
+
: undefined,
|
|
72
|
+
previousRevealProgress: series.type === 'line'
|
|
73
|
+
? this.getPreviousPathState(series.type, series.dataKey)
|
|
74
|
+
?.lineRevealProgress
|
|
75
|
+
: undefined,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
getAreaAnimationContext(series) {
|
|
79
|
+
const mode = this.getRenderMode();
|
|
80
|
+
if (mode === 'none') {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
mode,
|
|
85
|
+
duration: this.animation.duration,
|
|
86
|
+
easing: this.animation.easing,
|
|
87
|
+
previousSnapshot: this.getPreviousSeriesSnapshot(series.type, series.dataKey),
|
|
88
|
+
previousAreaPath: this.getPreviousPathState(series.type, series.dataKey)?.areaPath,
|
|
89
|
+
previousAreaRevealProgress: this.getPreviousPathState(series.type, series.dataKey)?.areaRevealProgress,
|
|
90
|
+
previousLinePath: this.getPreviousPathState(series.type, series.dataKey)?.areaLinePath,
|
|
91
|
+
previousLineRevealProgress: this.getPreviousPathState(series.type, series.dataKey)?.areaLineRevealProgress,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
getBarAnimationContext(series, baselineValuePosition) {
|
|
95
|
+
const mode = this.getRenderMode();
|
|
96
|
+
if (mode === 'none') {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
mode,
|
|
101
|
+
duration: this.animation.duration,
|
|
102
|
+
easing: this.animation.easing,
|
|
103
|
+
baselineValuePosition,
|
|
104
|
+
previousSnapshot: this.getPreviousSeriesSnapshot(series.type, series.dataKey),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
completeRender(result) {
|
|
108
|
+
this.lastSeriesSnapshot = result.snapshotCollection;
|
|
109
|
+
this.hasRenderedLive = true;
|
|
110
|
+
this.nextRenderAnimationMode = 'none';
|
|
111
|
+
this.pendingAnimationSnapshot = null;
|
|
112
|
+
this.pendingPathState = new Map();
|
|
113
|
+
if (result.transitions.length === 0) {
|
|
114
|
+
return Promise.resolve();
|
|
115
|
+
}
|
|
116
|
+
return Promise.allSettled(result.transitions).then(() => undefined);
|
|
117
|
+
}
|
|
118
|
+
getRenderMode() {
|
|
119
|
+
if (this.animation.duration === 0) {
|
|
120
|
+
return 'none';
|
|
121
|
+
}
|
|
122
|
+
return this.nextRenderAnimationMode;
|
|
123
|
+
}
|
|
124
|
+
getPreviousSeriesSnapshot(seriesType, dataKey) {
|
|
125
|
+
return this.pendingAnimationSnapshot?.get(createXYSeriesSnapshotId(seriesType, dataKey));
|
|
126
|
+
}
|
|
127
|
+
getPreviousPathState(seriesType, dataKey) {
|
|
128
|
+
return this.pendingPathState.get(createXYSeriesSnapshotId(seriesType, dataKey));
|
|
129
|
+
}
|
|
130
|
+
}
|
package/dist/y-axis.d.ts
CHANGED
|
@@ -5,12 +5,14 @@ export declare class YAxis implements LayoutAwareComponent<YAxisConfigBase> {
|
|
|
5
5
|
readonly type: "yAxis";
|
|
6
6
|
readonly display: boolean;
|
|
7
7
|
private readonly tickPadding;
|
|
8
|
-
private
|
|
9
|
-
private readonly maxLabelWidth
|
|
8
|
+
private fontSize;
|
|
9
|
+
private readonly maxLabelWidth?;
|
|
10
10
|
private readonly tickFormat;
|
|
11
11
|
private readonly rotatedLabels;
|
|
12
12
|
private readonly oversizedBehavior;
|
|
13
|
+
private estimatedWidth;
|
|
13
14
|
readonly exportHooks?: ExportHooks<YAxisConfigBase>;
|
|
15
|
+
private resolveFontSizeValue;
|
|
14
16
|
constructor(config?: YAxisConfig);
|
|
15
17
|
getExportConfig(): YAxisConfigBase;
|
|
16
18
|
createExportComponent(override?: Partial<YAxisConfigBase>): LayoutAwareComponent<YAxisConfigBase>;
|
|
@@ -18,8 +20,11 @@ export declare class YAxis implements LayoutAwareComponent<YAxisConfigBase> {
|
|
|
18
20
|
* Returns the space required by the y-axis
|
|
19
21
|
*/
|
|
20
22
|
getRequiredSpace(): ComponentSpace;
|
|
23
|
+
estimateLayoutSpace(labels: unknown[], theme: ChartTheme, svg: SVGSVGElement): void;
|
|
24
|
+
clearEstimatedSpace(): void;
|
|
21
25
|
render(svg: Selection<SVGSVGElement, undefined, null, undefined>, y: D3Scale, theme: ChartTheme, xPosition: number): void;
|
|
22
26
|
private applyLabelConstraints;
|
|
27
|
+
private measureLabelDimensions;
|
|
23
28
|
private wrapTextElement;
|
|
24
29
|
private addTitleTooltip;
|
|
25
30
|
}
|
package/dist/y-axis.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { axisLeft } from 'd3';
|
|
2
2
|
import { measureTextWidth, truncateText, wrapText, mergeDeep } from './utils.js';
|
|
3
3
|
export class YAxis {
|
|
4
|
+
resolveFontSizeValue(fontSize, fallback) {
|
|
5
|
+
if (typeof fontSize === 'number' && Number.isFinite(fontSize)) {
|
|
6
|
+
return fontSize;
|
|
7
|
+
}
|
|
8
|
+
if (typeof fontSize === 'string') {
|
|
9
|
+
const parsedFontSize = parseFloat(fontSize);
|
|
10
|
+
if (Number.isFinite(parsedFontSize)) {
|
|
11
|
+
return parsedFontSize;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
4
16
|
constructor(config) {
|
|
5
17
|
Object.defineProperty(this, "type", {
|
|
6
18
|
enumerable: true,
|
|
@@ -50,18 +62,25 @@ export class YAxis {
|
|
|
50
62
|
writable: true,
|
|
51
63
|
value: void 0
|
|
52
64
|
});
|
|
65
|
+
Object.defineProperty(this, "estimatedWidth", {
|
|
66
|
+
enumerable: true,
|
|
67
|
+
configurable: true,
|
|
68
|
+
writable: true,
|
|
69
|
+
value: null
|
|
70
|
+
});
|
|
53
71
|
Object.defineProperty(this, "exportHooks", {
|
|
54
72
|
enumerable: true,
|
|
55
73
|
configurable: true,
|
|
56
74
|
writable: true,
|
|
57
75
|
value: void 0
|
|
58
76
|
});
|
|
59
|
-
|
|
60
|
-
this.
|
|
61
|
-
this.
|
|
62
|
-
this.
|
|
63
|
-
this.
|
|
64
|
-
this.
|
|
77
|
+
const { display = true, tickFormat = null, rotatedLabels = false, maxLabelWidth, oversizedBehavior = 'truncate', exportHooks, } = config ?? {};
|
|
78
|
+
this.display = display;
|
|
79
|
+
this.tickFormat = tickFormat;
|
|
80
|
+
this.rotatedLabels = rotatedLabels;
|
|
81
|
+
this.maxLabelWidth = maxLabelWidth;
|
|
82
|
+
this.oversizedBehavior = oversizedBehavior;
|
|
83
|
+
this.exportHooks = exportHooks;
|
|
65
84
|
}
|
|
66
85
|
getExportConfig() {
|
|
67
86
|
return {
|
|
@@ -90,20 +109,54 @@ export class YAxis {
|
|
|
90
109
|
position: 'left',
|
|
91
110
|
};
|
|
92
111
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
112
|
+
if (this.estimatedWidth !== null) {
|
|
113
|
+
return {
|
|
114
|
+
width: this.estimatedWidth,
|
|
115
|
+
height: 0,
|
|
116
|
+
position: 'left',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const fallbackLabelWidth = this.maxLabelWidth ?? this.fontSize;
|
|
120
|
+
const width = fallbackLabelWidth + this.tickPadding;
|
|
97
121
|
return {
|
|
98
122
|
width,
|
|
99
123
|
height: 0, // Y-axis spans full height
|
|
100
124
|
position: 'left',
|
|
101
125
|
};
|
|
102
126
|
}
|
|
127
|
+
estimateLayoutSpace(labels, theme, svg) {
|
|
128
|
+
if (!labels.length) {
|
|
129
|
+
this.estimatedWidth = 0;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
this.fontSize = this.resolveFontSizeValue(theme.axis.fontSize, this.fontSize);
|
|
133
|
+
const fontSize = this.fontSize;
|
|
134
|
+
const fontFamily = theme.axis.fontFamily;
|
|
135
|
+
const fontWeight = theme.axis.fontWeight || 'normal';
|
|
136
|
+
let maxWidth = 0;
|
|
137
|
+
let maxHeight = 0;
|
|
138
|
+
for (const label of labels) {
|
|
139
|
+
const text = String(label ?? '');
|
|
140
|
+
if (!text)
|
|
141
|
+
continue;
|
|
142
|
+
const { width, height } = this.measureLabelDimensions(text, fontSize, fontFamily, fontWeight, svg);
|
|
143
|
+
maxWidth = Math.max(maxWidth, width);
|
|
144
|
+
maxHeight = Math.max(maxHeight, height);
|
|
145
|
+
}
|
|
146
|
+
const radians = Math.PI / 4;
|
|
147
|
+
const labelFootprint = this.rotatedLabels
|
|
148
|
+
? Math.cos(radians) * maxWidth + Math.sin(radians) * maxHeight
|
|
149
|
+
: maxWidth;
|
|
150
|
+
this.estimatedWidth = this.tickPadding + labelFootprint;
|
|
151
|
+
}
|
|
152
|
+
clearEstimatedSpace() {
|
|
153
|
+
this.estimatedWidth = null;
|
|
154
|
+
}
|
|
103
155
|
render(svg, y, theme, xPosition) {
|
|
104
156
|
if (!this.display) {
|
|
105
157
|
return;
|
|
106
158
|
}
|
|
159
|
+
this.fontSize = this.resolveFontSizeValue(theme.axis.fontSize, this.fontSize);
|
|
107
160
|
const axis = axisLeft(y).tickSize(0).tickPadding(this.tickPadding);
|
|
108
161
|
// Apply tick formatting if specified
|
|
109
162
|
if (this.tickFormat) {
|
|
@@ -138,6 +191,9 @@ export class YAxis {
|
|
|
138
191
|
axisGroup.selectAll('.domain').remove();
|
|
139
192
|
}
|
|
140
193
|
applyLabelConstraints(axisGroup, svg, fontSize, fontFamily, fontWeight) {
|
|
194
|
+
if (this.maxLabelWidth === undefined) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
141
197
|
const maxWidth = this.maxLabelWidth;
|
|
142
198
|
const behavior = this.oversizedBehavior;
|
|
143
199
|
axisGroup
|
|
@@ -170,6 +226,39 @@ export class YAxis {
|
|
|
170
226
|
}
|
|
171
227
|
});
|
|
172
228
|
}
|
|
229
|
+
measureLabelDimensions(text, fontSize, fontFamily, fontWeight, svg) {
|
|
230
|
+
const textWidth = measureTextWidth(text, fontSize, fontFamily, fontWeight, svg);
|
|
231
|
+
const lineHeight = fontSize * 1.2;
|
|
232
|
+
if (this.maxLabelWidth === undefined ||
|
|
233
|
+
textWidth <= this.maxLabelWidth) {
|
|
234
|
+
return {
|
|
235
|
+
width: textWidth,
|
|
236
|
+
height: fontSize,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
switch (this.oversizedBehavior) {
|
|
240
|
+
case 'truncate':
|
|
241
|
+
return {
|
|
242
|
+
width: this.maxLabelWidth,
|
|
243
|
+
height: fontSize,
|
|
244
|
+
};
|
|
245
|
+
case 'wrap': {
|
|
246
|
+
const lines = wrapText(text, this.maxLabelWidth, fontSize, fontFamily, fontWeight, svg);
|
|
247
|
+
const widestLine = lines.reduce((widest, line) => {
|
|
248
|
+
return Math.max(widest, measureTextWidth(line, fontSize, fontFamily, fontWeight, svg));
|
|
249
|
+
}, 0);
|
|
250
|
+
return {
|
|
251
|
+
width: widestLine,
|
|
252
|
+
height: Math.max(lines.length, 1) * lineHeight,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
case 'hide':
|
|
256
|
+
return {
|
|
257
|
+
width: 0,
|
|
258
|
+
height: 0,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
173
262
|
wrapTextElement(textEl, lines, originalText) {
|
|
174
263
|
// Clear existing content
|
|
175
264
|
textEl.textContent = '';
|
package/docs/components.md
CHANGED
|
@@ -8,12 +8,18 @@ Renders the X axis.
|
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
10
|
new XAxis({
|
|
11
|
-
display?: boolean,
|
|
11
|
+
display?: boolean, // Render axis and reserve layout space (default: true)
|
|
12
12
|
dataKey?: string, // Key in data objects for X values (auto-detected if omitted)
|
|
13
13
|
labelKey?: string, // Optional display label key (uses dataKey values if omitted)
|
|
14
14
|
groupLabelKey?: string, // Optional key used for second-row grouped labels
|
|
15
15
|
showGroupLabels?: boolean, // Show second-row grouped labels (default: false)
|
|
16
16
|
groupLabelGap?: number, // Vertical gap between tick row and grouped row
|
|
17
|
+
rotatedLabels?: boolean, // Rotate tick labels -45deg
|
|
18
|
+
maxLabelWidth?: number, // Optional cap for tick-label width
|
|
19
|
+
oversizedBehavior?: 'truncate' | 'wrap' | 'hide', // Only applies when maxLabelWidth is set
|
|
20
|
+
autoHideOverlapping?: boolean, // Automatically hide overlapping labels
|
|
21
|
+
minLabelGap?: number, // Minimum gap between visible labels when auto-hide is enabled
|
|
22
|
+
preserveEndLabels?: boolean, // Keep first/last labels visible when auto-hide is enabled
|
|
17
23
|
tickFormat?: string | ((value: string | number | Date) => string) | null
|
|
18
24
|
})
|
|
19
25
|
```
|
|
@@ -54,10 +60,17 @@ Renders the Y axis.
|
|
|
54
60
|
```typescript
|
|
55
61
|
new YAxis({
|
|
56
62
|
display?: boolean, // Render axis and reserve layout space (default: true)
|
|
63
|
+
rotatedLabels?: boolean, // Rotate tick labels -45deg
|
|
64
|
+
maxLabelWidth?: number, // Optional cap for tick-label width
|
|
65
|
+
oversizedBehavior?: 'truncate' | 'wrap' | 'hide', // Only applies when maxLabelWidth is set
|
|
57
66
|
tickFormat?: string | ((value: string | number | Date) => string) | null
|
|
58
67
|
})
|
|
59
68
|
```
|
|
60
69
|
|
|
70
|
+
When `maxLabelWidth` is omitted, `YAxis` reserves the measured width of its
|
|
71
|
+
rendered tick labels. Set `maxLabelWidth` to cap the reserved width and use
|
|
72
|
+
`oversizedBehavior` to control truncation, wrapping, or hiding.
|
|
73
|
+
|
|
61
74
|
### Format Examples
|
|
62
75
|
|
|
63
76
|
```javascript
|
|
@@ -106,6 +119,14 @@ Renders interactive tooltips on hover and keyboard focus.
|
|
|
106
119
|
|
|
107
120
|
```typescript
|
|
108
121
|
new Tooltip({
|
|
122
|
+
mode?: 'shared' | 'split',
|
|
123
|
+
position?: 'side' | 'vertical',
|
|
124
|
+
barAnchorPosition?: 'top' | 'middle',
|
|
125
|
+
transition?: {
|
|
126
|
+
show?: boolean,
|
|
127
|
+
duration?: number,
|
|
128
|
+
easing?: string
|
|
129
|
+
},
|
|
109
130
|
formatter?: (dataKey: string, value: DataValue, data: DataItem) => string
|
|
110
131
|
})
|
|
111
132
|
```
|
|
@@ -116,6 +137,34 @@ The formatter receives:
|
|
|
116
137
|
- `value` - The data value at this point
|
|
117
138
|
- `data` - The full data item object
|
|
118
139
|
|
|
140
|
+
Tooltip modes:
|
|
141
|
+
|
|
142
|
+
- `shared` - One tooltip per hovered category/value group
|
|
143
|
+
- `split` - Default. One tooltip per visible series at the hovered category/value group
|
|
144
|
+
|
|
145
|
+
Split tooltip positions:
|
|
146
|
+
|
|
147
|
+
- `side` - Default. Place split tooltips to the side of each data point
|
|
148
|
+
- `vertical` - Place split tooltips above or below each data point
|
|
149
|
+
|
|
150
|
+
When `split` mode is active, `customFormatter` receives the current series only for
|
|
151
|
+
that tooltip box instead of the full visible series list. Split tooltips include
|
|
152
|
+
directional arrows and avoid overlapping on the same side of the chart when
|
|
153
|
+
possible.
|
|
154
|
+
|
|
155
|
+
For bars, `barAnchorPosition` controls whether split tooltips point to the `top`
|
|
156
|
+
or `middle` of each bar.
|
|
157
|
+
|
|
158
|
+
Set `transition.show: true` to fade tooltips in and out. Tooltip position and
|
|
159
|
+
connector geometry update immediately; only opacity and the small entrance
|
|
160
|
+
offset transition.
|
|
161
|
+
|
|
162
|
+
Tooltip box, text, connector, and arrow colors use `theme.tooltip`.
|
|
163
|
+
|
|
164
|
+
Formatter, label formatter, and custom formatter output is inserted as HTML.
|
|
165
|
+
Only return trusted content or sanitize user-provided strings before returning
|
|
166
|
+
them from formatter callbacks.
|
|
167
|
+
|
|
119
168
|
### Example
|
|
120
169
|
|
|
121
170
|
```javascript
|
package/docs/getting-started.md
CHANGED
|
@@ -76,6 +76,41 @@ const chart = new XYChart({
|
|
|
76
76
|
});
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
## Lazy Loading
|
|
80
|
+
|
|
81
|
+
When a page contains charts further down the document, you can defer the chart
|
|
82
|
+
imports until the container is near the viewport.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { mountChartWhenVisible } from '@internetstiftelsen/charts/lazy-mount';
|
|
86
|
+
|
|
87
|
+
const lazyChart = mountChartWhenVisible(
|
|
88
|
+
'#chart-container',
|
|
89
|
+
async (container) => {
|
|
90
|
+
const [{ XYChart }, { Line }, { XAxis }, { YAxis }] = await Promise.all([import('@internetstiftelsen/charts/xy-chart'), import('@internetstiftelsen/charts/line'), import('@internetstiftelsen/charts/x-axis'), import('@internetstiftelsen/charts/y-axis')]);
|
|
91
|
+
|
|
92
|
+
const chart = new XYChart({ data });
|
|
93
|
+
chart
|
|
94
|
+
.addChild(new XAxis({ dataKey: 'date' }))
|
|
95
|
+
.addChild(new YAxis())
|
|
96
|
+
.addChild(new Line({ dataKey: 'revenue' }));
|
|
97
|
+
|
|
98
|
+
chart.render(container);
|
|
99
|
+
return chart;
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
rootMargin: '240px 0px',
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Optional if you want to trigger the load early
|
|
107
|
+
await lazyChart.load();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`mountChartWhenVisible` keeps the loading strategy separate from chart
|
|
111
|
+
construction, which makes it a good building block for custom wrappers or
|
|
112
|
+
attribute-driven mounting later on.
|
|
113
|
+
|
|
79
114
|
## Grouping Charts
|
|
80
115
|
|
|
81
116
|
```javascript
|
package/docs/theming.md
CHANGED
|
@@ -27,6 +27,14 @@ const chart = new XYChart({
|
|
|
27
27
|
fontFamily: 'Inter, sans-serif',
|
|
28
28
|
fontSize: 12,
|
|
29
29
|
},
|
|
30
|
+
tooltip: {
|
|
31
|
+
background: '#102030',
|
|
32
|
+
border: '#405060',
|
|
33
|
+
color: '#f7fafc',
|
|
34
|
+
fontFamily: 'Inter, sans-serif',
|
|
35
|
+
fontSize: 13,
|
|
36
|
+
fontWeight: '600',
|
|
37
|
+
},
|
|
30
38
|
},
|
|
31
39
|
});
|
|
32
40
|
```
|
|
@@ -51,6 +59,12 @@ import { defaultTheme, newspaperTheme, themes } from '@internetstiftelsen/charts
|
|
|
51
59
|
| `legend.paddingX` | `number` | `0` | Horizontal legend layout padding |
|
|
52
60
|
| `legend.itemSpacingX` | `number` | `20` | Horizontal spacing between legend items |
|
|
53
61
|
| `legend.itemSpacingY` | `number` | `8` | Vertical spacing between wrapped legend rows |
|
|
62
|
+
| `tooltip.background` | `string` | `'#ffffff'` | Tooltip box and arrow fill color |
|
|
63
|
+
| `tooltip.border` | `string` | `'#dddddd'` | Tooltip box, connector, and arrow border |
|
|
64
|
+
| `tooltip.color` | `string` | `'#1f2a36'` | Tooltip text color |
|
|
65
|
+
| `tooltip.fontFamily` | `string` | - | Tooltip text font |
|
|
66
|
+
| `tooltip.fontSize` | `number` | `12` | Tooltip text size in pixels |
|
|
67
|
+
| `tooltip.fontWeight` | `string` | `'normal'` | Tooltip text weight |
|
|
54
68
|
|
|
55
69
|
---
|
|
56
70
|
|
package/docs/xy-chart.md
CHANGED
|
@@ -21,6 +21,7 @@ new XYChart(config: XYChartConfig)
|
|
|
21
21
|
| `responsive` | `ResponsiveConfig` | - | Container-query responsive overrides (theme + components) |
|
|
22
22
|
| `barStack` | `BarStackConfig` | `{ mode: 'normal', gap: 0.1, reverseSeries: false }` | Bar stacking configuration |
|
|
23
23
|
| `areaStack` | `AreaStackConfig` | `{ mode: 'none' }` | Area stacking configuration |
|
|
24
|
+
| `animate` | `boolean \| XYAnimationConfig` | `false` | Opt-in XY series animation for initial render and `update()` |
|
|
24
25
|
|
|
25
26
|
### Theme Options
|
|
26
27
|
|
|
@@ -223,6 +224,45 @@ is measured after declarative breakpoint overrides are merged. It receives
|
|
|
223
224
|
`context.activeBreakpoints` with every match and `context.breakpoint` as the
|
|
224
225
|
last matching breakpoint name.
|
|
225
226
|
|
|
227
|
+
## Animation
|
|
228
|
+
|
|
229
|
+
Use `animate` when you want XY series marks to animate on the first render and
|
|
230
|
+
when calling `chart.update(...)`.
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
const chart = new XYChart({
|
|
234
|
+
data,
|
|
235
|
+
animate: {
|
|
236
|
+
duration: 700,
|
|
237
|
+
easing: 'ease-in-out',
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`XYAnimationConfig` supports:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
animate: boolean | {
|
|
246
|
+
show?: boolean, // default: true when object form is used
|
|
247
|
+
duration?: number, // ms, default: 700
|
|
248
|
+
easing?:
|
|
249
|
+
| 'linear'
|
|
250
|
+
| 'ease-in'
|
|
251
|
+
| 'ease-out'
|
|
252
|
+
| 'ease-in-out'
|
|
253
|
+
| 'bounce-out'
|
|
254
|
+
| 'elastic-out'
|
|
255
|
+
| `linear(...)`
|
|
256
|
+
| ((t: number) => number),
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Notes:
|
|
261
|
+
|
|
262
|
+
- Animation is off by default.
|
|
263
|
+
- Animates XY series marks only. Axes, legends, tooltips, and value labels remain static.
|
|
264
|
+
- Visual exports always render the final static state, even when the live chart is animated.
|
|
265
|
+
|
|
226
266
|
## Validation
|
|
227
267
|
|
|
228
268
|
`XYChart` validates configuration and series data early:
|
|
@@ -256,6 +296,9 @@ chart.render('#chart-container');
|
|
|
256
296
|
chart.render(document.getElementById('chart-container'));
|
|
257
297
|
```
|
|
258
298
|
|
|
299
|
+
When `animate` is enabled, the first render returns immediately while the series
|
|
300
|
+
transition continues in the background.
|
|
301
|
+
|
|
259
302
|
### update(data)
|
|
260
303
|
|
|
261
304
|
Updates the chart with new data and re-renders.
|
|
@@ -264,6 +307,18 @@ Updates the chart with new data and re-renders.
|
|
|
264
307
|
chart.update(newData);
|
|
265
308
|
```
|
|
266
309
|
|
|
310
|
+
When `animate` is enabled, `update()` animates XY series marks from the previous
|
|
311
|
+
rendered geometry to the new one.
|
|
312
|
+
|
|
313
|
+
### whenReady()
|
|
314
|
+
|
|
315
|
+
Returns a promise that resolves after any pending async render work finishes.
|
|
316
|
+
For animated XY charts, this waits until the current series transitions finish.
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
await chart.whenReady();
|
|
320
|
+
```
|
|
321
|
+
|
|
267
322
|
### destroy()
|
|
268
323
|
|
|
269
324
|
Cleans up all resources, removes resize observer, and clears the chart from the DOM.
|
|
@@ -280,9 +335,13 @@ Renders a line series on the chart.
|
|
|
280
335
|
|
|
281
336
|
```typescript
|
|
282
337
|
new Line({
|
|
283
|
-
dataKey: string,
|
|
284
|
-
stroke?: string,
|
|
285
|
-
strokeWidth?: number,
|
|
338
|
+
dataKey: string, // Key in data objects for Y values (required)
|
|
339
|
+
stroke?: string, // Line color (auto-assigned if omitted)
|
|
340
|
+
strokeWidth?: number, // Line width in pixels (default: 2)
|
|
341
|
+
valueLabel?: {
|
|
342
|
+
show?: boolean,
|
|
343
|
+
formatter?: (dataKey, value, data) => string
|
|
344
|
+
} // Point value badges
|
|
286
345
|
})
|
|
287
346
|
```
|
|
288
347
|
|
|
@@ -309,7 +368,10 @@ new Scatter({
|
|
|
309
368
|
dataKey: string, // Key in data objects for Y values (required)
|
|
310
369
|
stroke?: string, // Point color (auto-assigned if omitted)
|
|
311
370
|
pointSize?: number, // Point radius in pixels (default: theme.line.point.size)
|
|
312
|
-
valueLabel?: {
|
|
371
|
+
valueLabel?: {
|
|
372
|
+
show?: boolean,
|
|
373
|
+
formatter?: (dataKey, value, data) => string
|
|
374
|
+
} // Point value badges
|
|
313
375
|
})
|
|
314
376
|
```
|
|
315
377
|
|
|
@@ -335,7 +397,8 @@ new Bar({
|
|
|
335
397
|
valueLabel?: {
|
|
336
398
|
show?: boolean,
|
|
337
399
|
position?: 'inside' | 'outside',
|
|
338
|
-
insidePosition?: 'top' | 'middle' | 'bottom'
|
|
400
|
+
insidePosition?: 'top' | 'middle' | 'bottom',
|
|
401
|
+
formatter?: (dataKey, value, data) => string
|
|
339
402
|
}
|
|
340
403
|
})
|
|
341
404
|
```
|
|
@@ -430,7 +493,18 @@ Bar charts support different stacking modes:
|
|
|
430
493
|
- `percent` - 100% stacked bars
|
|
431
494
|
- `layer` - Overlapping bars
|
|
432
495
|
|
|
433
|
-
|
|
496
|
+
XY charts use split tooltips by default, rendering one tooltip per visible
|
|
497
|
+
series at the hovered category. Use
|
|
498
|
+
`new Tooltip({ mode: 'shared' | 'split', position: 'side' | 'vertical' })`
|
|
499
|
+
to override the grouping and split-tooltip placement.
|
|
500
|
+
For bars, set `barAnchorPosition: 'top' | 'middle'` to choose whether split
|
|
501
|
+
tooltips point to the top or middle of each bar.
|
|
502
|
+
Use `transition: { show: true, duration: 120, easing: 'ease-out' }` to opt in
|
|
503
|
+
to softer tooltip opacity transitions without delaying position updates.
|
|
504
|
+
|
|
505
|
+
Use `barStack.reverseSeries: true` to reverse bar series display order for
|
|
506
|
+
rendering, legend entries, and split tooltip ordering without changing data
|
|
507
|
+
exports.
|
|
434
508
|
|
|
435
509
|
---
|
|
436
510
|
|
|
@@ -449,7 +523,10 @@ new Area({
|
|
|
449
523
|
baseline?: number, // Baseline for non-stacked area (default: 0)
|
|
450
524
|
showLine?: boolean, // Show top stroke line (default: true)
|
|
451
525
|
showPoints?: boolean, // Show points on top line (default: false)
|
|
452
|
-
valueLabel?: {
|
|
526
|
+
valueLabel?: {
|
|
527
|
+
show?: boolean,
|
|
528
|
+
formatter?: (dataKey, value, data) => string
|
|
529
|
+
} // Point value badges
|
|
453
530
|
})
|
|
454
531
|
```
|
|
455
532
|
|
|
@@ -468,6 +545,10 @@ chart.addChild(
|
|
|
468
545
|
);
|
|
469
546
|
```
|
|
470
547
|
|
|
548
|
+
For `Line`, `Scatter`, `Bar`, and `Area`, `valueLabel.formatter` receives
|
|
549
|
+
`(dataKey, value, data)`, where `value` is the parsed numeric series value used
|
|
550
|
+
for rendering that label.
|
|
551
|
+
|
|
471
552
|
### Area Stacking
|
|
472
553
|
|
|
473
554
|
Area charts support stacking when series share the same `stackId`:
|