@internetstiftelsen/charts 0.13.2 → 0.14.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 +4 -1
- package/dist/area.js +2 -1
- package/dist/bar.js +8 -4
- package/dist/base-chart.d.ts +1 -0
- package/dist/base-chart.js +2 -0
- package/dist/donut-chart.d.ts +19 -3
- package/dist/donut-chart.js +129 -25
- package/dist/easing.d.ts +1 -0
- package/dist/easing.js +30 -0
- package/dist/gauge-chart.d.ts +7 -2
- package/dist/gauge-chart.js +43 -18
- package/dist/line.js +2 -1
- package/dist/pie-chart.d.ts +19 -3
- package/dist/pie-chart.js +160 -59
- package/dist/radial-animation.d.ts +69 -0
- package/dist/radial-animation.js +416 -0
- package/dist/radial-chart-base.d.ts +24 -1
- package/dist/radial-chart-base.js +181 -0
- package/dist/scatter.js +2 -1
- package/dist/theme.d.ts +15 -0
- package/dist/theme.js +90 -4
- package/dist/tooltip.d.ts +10 -1
- package/dist/tooltip.js +116 -59
- package/dist/types.d.ts +1 -0
- package/dist/xy-motion/config.js +3 -0
- package/dist/xy-motion/types.d.ts +1 -1
- package/docs/donut-chart.md +57 -14
- package/docs/gauge-chart.md +14 -0
- package/docs/pie-chart.md +58 -16
- package/docs/theming.md +17 -12
- package/docs/xy-chart.md +10 -0
- package/package.json +26 -26
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type PieArcDatum } from 'd3';
|
|
2
|
+
export type RadialAnimationEasingPreset = 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'bounce-out' | 'elastic-out' | 'spring-out';
|
|
3
|
+
export type RadialAnimationConfig = {
|
|
4
|
+
show?: boolean;
|
|
5
|
+
duration?: number;
|
|
6
|
+
easing?: RadialAnimationEasingPreset | `linear(${string})` | ((progress: number) => number);
|
|
7
|
+
};
|
|
8
|
+
export type NormalizedRadialAnimation = {
|
|
9
|
+
show: boolean;
|
|
10
|
+
duration: number;
|
|
11
|
+
easing: (progress: number) => number;
|
|
12
|
+
};
|
|
13
|
+
export type RadialRenderAnimationMode = 'none' | 'initial' | 'update';
|
|
14
|
+
export type RadialSegmentAnimationData = {
|
|
15
|
+
label: string;
|
|
16
|
+
value: number;
|
|
17
|
+
};
|
|
18
|
+
type RadialSegmentSnapshot = {
|
|
19
|
+
startAngle: number;
|
|
20
|
+
endAngle: number;
|
|
21
|
+
padAngle: number;
|
|
22
|
+
value: number;
|
|
23
|
+
index: number;
|
|
24
|
+
};
|
|
25
|
+
export type RadialSegmentSnapshotCollection = Map<string, RadialSegmentSnapshot>;
|
|
26
|
+
export type RadialAnimationContext = {
|
|
27
|
+
mode: Exclude<RadialRenderAnimationMode, 'none'>;
|
|
28
|
+
duration: number;
|
|
29
|
+
easing: (progress: number) => number;
|
|
30
|
+
previousSnapshot?: RadialSegmentSnapshotCollection;
|
|
31
|
+
};
|
|
32
|
+
export type RadialArcShape = {
|
|
33
|
+
innerRadius: number;
|
|
34
|
+
outerRadius: number;
|
|
35
|
+
cornerRadius: number;
|
|
36
|
+
};
|
|
37
|
+
export type RadialAnimatedArcDatum<TData extends RadialSegmentAnimationData> = {
|
|
38
|
+
key: string;
|
|
39
|
+
datum: PieArcDatum<TData>;
|
|
40
|
+
startDatum: PieArcDatum<TData>;
|
|
41
|
+
endDatum: PieArcDatum<TData>;
|
|
42
|
+
startShape: RadialArcShape;
|
|
43
|
+
endShape: RadialArcShape;
|
|
44
|
+
initialOpacity: number;
|
|
45
|
+
finalOpacity: number;
|
|
46
|
+
interactive: boolean;
|
|
47
|
+
};
|
|
48
|
+
export declare function normalizeRadialAnimationConfig(config: boolean | RadialAnimationConfig | undefined, chartName: string): NormalizedRadialAnimation;
|
|
49
|
+
export declare class RadialMotionController {
|
|
50
|
+
private readonly animation;
|
|
51
|
+
private hasRenderedLive;
|
|
52
|
+
private nextRenderAnimationMode;
|
|
53
|
+
private pendingAnimationSnapshot;
|
|
54
|
+
private lastSegmentSnapshot;
|
|
55
|
+
constructor(animation: NormalizedRadialAnimation);
|
|
56
|
+
prepareForUpdate(): void;
|
|
57
|
+
getAnimationContext(): RadialAnimationContext | undefined;
|
|
58
|
+
completeRender<TData extends RadialSegmentAnimationData>(pieData: Array<PieArcDatum<TData>>, transitions: Promise<void>[]): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
export declare function buildRadialSegmentKeys<TData extends RadialSegmentAnimationData>(pieData: Array<PieArcDatum<TData>>): string[];
|
|
61
|
+
export declare function buildRadialAnimatedArcData<TData extends RadialSegmentAnimationData>(pieData: Array<PieArcDatum<TData>>, allSegments: TData[], context: RadialAnimationContext | undefined, shape: RadialArcShape, exitTargetPieData?: Array<PieArcDatum<TData>>): Array<RadialAnimatedArcDatum<TData>>;
|
|
62
|
+
export declare function buildRadialExitTargetPieData<TData extends RadialSegmentAnimationData>(pieGenerator: (segments: TData[]) => Array<PieArcDatum<TData>>, visibleSegments: TData[], allSegments: TData[], context: RadialAnimationContext | undefined, currentPieData: Array<PieArcDatum<TData>>): Array<PieArcDatum<TData>>;
|
|
63
|
+
export declare function renderRadialArcDatum<TData extends RadialSegmentAnimationData>(datum: PieArcDatum<TData>, shape: RadialArcShape): string;
|
|
64
|
+
export declare function interpolateRadialArcDatum<TData extends RadialSegmentAnimationData>(startDatum: PieArcDatum<TData>, endDatum: PieArcDatum<TData>): (progress: number) => PieArcDatum<TData>;
|
|
65
|
+
export declare function interpolateRadialArcShape(startShape: RadialArcShape, endShape: RadialArcShape): (progress: number) => RadialArcShape;
|
|
66
|
+
export declare function createTransitionCompletionPromise(transition: {
|
|
67
|
+
end: () => Promise<unknown>;
|
|
68
|
+
}): Promise<void>;
|
|
69
|
+
export {};
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { arc, easeBounceOut, easeCubicIn, easeCubicInOut, easeCubicOut, easeElasticOut, easeLinear, interpolateNumber, } from 'd3';
|
|
2
|
+
import { createCubicBezierEasing } from './easing.js';
|
|
3
|
+
import { ChartValidationError, ChartValidator } from './validation.js';
|
|
4
|
+
const DEFAULT_ANIMATE = false;
|
|
5
|
+
const DEFAULT_ANIMATION_DURATION_MS = 700;
|
|
6
|
+
const DEFAULT_ANIMATION_EASING_PRESET = 'ease-in-out';
|
|
7
|
+
const easeSpringOut = createCubicBezierEasing(0.85, 0, 0.15, 1);
|
|
8
|
+
const RADIAL_ANIMATION_EASING_PRESETS = {
|
|
9
|
+
linear: easeLinear,
|
|
10
|
+
'ease-in': easeCubicIn,
|
|
11
|
+
'ease-out': easeCubicOut,
|
|
12
|
+
'ease-in-out': easeCubicInOut,
|
|
13
|
+
'bounce-out': easeBounceOut,
|
|
14
|
+
'elastic-out': easeElasticOut,
|
|
15
|
+
'spring-out': easeSpringOut,
|
|
16
|
+
};
|
|
17
|
+
export function normalizeRadialAnimationConfig(config, chartName) {
|
|
18
|
+
if (config === undefined) {
|
|
19
|
+
return {
|
|
20
|
+
show: DEFAULT_ANIMATE,
|
|
21
|
+
duration: DEFAULT_ANIMATION_DURATION_MS,
|
|
22
|
+
easing: RADIAL_ANIMATION_EASING_PRESETS[DEFAULT_ANIMATION_EASING_PRESET],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (typeof config === 'boolean') {
|
|
26
|
+
return {
|
|
27
|
+
show: config,
|
|
28
|
+
duration: DEFAULT_ANIMATION_DURATION_MS,
|
|
29
|
+
easing: RADIAL_ANIMATION_EASING_PRESETS[DEFAULT_ANIMATION_EASING_PRESET],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const normalized = {
|
|
33
|
+
show: config.show ?? true,
|
|
34
|
+
duration: config.duration ?? DEFAULT_ANIMATION_DURATION_MS,
|
|
35
|
+
easing: resolveRadialAnimationEasing(config.easing, chartName),
|
|
36
|
+
};
|
|
37
|
+
if (!Number.isFinite(normalized.duration) || normalized.duration < 0) {
|
|
38
|
+
throw new ChartValidationError(`${chartName}: animate.duration must be >= 0, received '${normalized.duration}'`);
|
|
39
|
+
}
|
|
40
|
+
return normalized;
|
|
41
|
+
}
|
|
42
|
+
function resolveRadialAnimationEasing(easing, chartName) {
|
|
43
|
+
if (!easing) {
|
|
44
|
+
return RADIAL_ANIMATION_EASING_PRESETS[DEFAULT_ANIMATION_EASING_PRESET];
|
|
45
|
+
}
|
|
46
|
+
if (typeof easing === 'function') {
|
|
47
|
+
return easing;
|
|
48
|
+
}
|
|
49
|
+
if (easing in RADIAL_ANIMATION_EASING_PRESETS) {
|
|
50
|
+
return RADIAL_ANIMATION_EASING_PRESETS[easing];
|
|
51
|
+
}
|
|
52
|
+
if (easing.startsWith('linear(')) {
|
|
53
|
+
const parsedCssLinear = parseCssLinearEasing(easing);
|
|
54
|
+
if (parsedCssLinear) {
|
|
55
|
+
return parsedCssLinear;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
ChartValidator.warn(`${chartName}: unsupported animate.easing '${easing}', falling back to '${DEFAULT_ANIMATION_EASING_PRESET}'`);
|
|
59
|
+
return RADIAL_ANIMATION_EASING_PRESETS[DEFAULT_ANIMATION_EASING_PRESET];
|
|
60
|
+
}
|
|
61
|
+
function parseCssLinearEasing(cssLinearEasing) {
|
|
62
|
+
const normalized = cssLinearEasing.trim();
|
|
63
|
+
if (!normalized.startsWith('linear(') || !normalized.endsWith(')')) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const body = normalized.slice('linear('.length, -1);
|
|
67
|
+
const tokens = body
|
|
68
|
+
.split(',')
|
|
69
|
+
.map((token) => token.trim())
|
|
70
|
+
.filter((token) => token.length > 0);
|
|
71
|
+
if (tokens.length < 2) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const rawStops = tokens.map((token) => parseLinearEasingStop(token));
|
|
75
|
+
if (rawStops.some((stop) => stop === null)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const stops = rawStops;
|
|
79
|
+
const firstPosition = stops[0].position ?? 0;
|
|
80
|
+
const lastPosition = stops[stops.length - 1].position ?? 1;
|
|
81
|
+
if (!hasValidLinearStopPositions(stops, firstPosition, lastPosition)) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
fillMissingLinearStopPositions(stops, firstPosition, lastPosition);
|
|
85
|
+
return (progress) => sampleLinearEasing(stops, progress);
|
|
86
|
+
}
|
|
87
|
+
function parseLinearEasingStop(token) {
|
|
88
|
+
const parts = token
|
|
89
|
+
.trim()
|
|
90
|
+
.split(/\s+/)
|
|
91
|
+
.map((part) => part.trim())
|
|
92
|
+
.filter((part) => part.length > 0);
|
|
93
|
+
if (parts.length === 0 || parts.length > 2) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const value = Number(parts[0]);
|
|
97
|
+
if (!Number.isFinite(value)) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
if (parts.length === 1) {
|
|
101
|
+
return { value, position: undefined };
|
|
102
|
+
}
|
|
103
|
+
const positionText = parts[1];
|
|
104
|
+
if (!positionText.endsWith('%')) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const percentValue = Number(positionText.slice(0, -1));
|
|
108
|
+
if (!Number.isFinite(percentValue)) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
value,
|
|
113
|
+
position: percentValue / 100,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function hasValidLinearStopPositions(stops, firstPosition, lastPosition) {
|
|
117
|
+
if (lastPosition <= firstPosition) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
let previousPosition = firstPosition;
|
|
121
|
+
for (let index = 1; index < stops.length; index += 1) {
|
|
122
|
+
const stopPosition = stops[index].position;
|
|
123
|
+
if (stopPosition === undefined) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (stopPosition < previousPosition) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
previousPosition = stopPosition;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
function fillMissingLinearStopPositions(stops, firstPosition, lastPosition) {
|
|
134
|
+
stops[0].position = firstPosition;
|
|
135
|
+
stops[stops.length - 1].position = lastPosition;
|
|
136
|
+
let index = 1;
|
|
137
|
+
while (index < stops.length - 1) {
|
|
138
|
+
if (stops[index].position !== undefined) {
|
|
139
|
+
index += 1;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const startIndex = index - 1;
|
|
143
|
+
let endIndex = index + 1;
|
|
144
|
+
while (endIndex < stops.length &&
|
|
145
|
+
stops[endIndex].position === undefined) {
|
|
146
|
+
endIndex += 1;
|
|
147
|
+
}
|
|
148
|
+
const startPosition = stops[startIndex].position ?? firstPosition;
|
|
149
|
+
const endPosition = stops[endIndex]?.position ?? lastPosition;
|
|
150
|
+
const missingCount = endIndex - startIndex;
|
|
151
|
+
for (let fillIndex = startIndex + 1; fillIndex < endIndex; fillIndex += 1) {
|
|
152
|
+
const ratio = (fillIndex - startIndex) / missingCount;
|
|
153
|
+
stops[fillIndex].position =
|
|
154
|
+
startPosition + (endPosition - startPosition) * ratio;
|
|
155
|
+
}
|
|
156
|
+
index = endIndex;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function sampleLinearEasing(stops, progress) {
|
|
160
|
+
const clampedProgress = Math.max(0, Math.min(1, progress));
|
|
161
|
+
if (clampedProgress <= (stops[0].position ?? 0)) {
|
|
162
|
+
return stops[0].value;
|
|
163
|
+
}
|
|
164
|
+
for (let index = 1; index < stops.length; index += 1) {
|
|
165
|
+
const previousStop = stops[index - 1];
|
|
166
|
+
const nextStop = stops[index];
|
|
167
|
+
const start = previousStop.position ?? 0;
|
|
168
|
+
const end = nextStop.position ?? 1;
|
|
169
|
+
if (clampedProgress > end) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (end === start) {
|
|
173
|
+
return nextStop.value;
|
|
174
|
+
}
|
|
175
|
+
const ratio = (clampedProgress - start) / (end - start);
|
|
176
|
+
return (previousStop.value + (nextStop.value - previousStop.value) * ratio);
|
|
177
|
+
}
|
|
178
|
+
return stops[stops.length - 1].value;
|
|
179
|
+
}
|
|
180
|
+
export class RadialMotionController {
|
|
181
|
+
constructor(animation) {
|
|
182
|
+
Object.defineProperty(this, "animation", {
|
|
183
|
+
enumerable: true,
|
|
184
|
+
configurable: true,
|
|
185
|
+
writable: true,
|
|
186
|
+
value: void 0
|
|
187
|
+
});
|
|
188
|
+
Object.defineProperty(this, "hasRenderedLive", {
|
|
189
|
+
enumerable: true,
|
|
190
|
+
configurable: true,
|
|
191
|
+
writable: true,
|
|
192
|
+
value: false
|
|
193
|
+
});
|
|
194
|
+
Object.defineProperty(this, "nextRenderAnimationMode", {
|
|
195
|
+
enumerable: true,
|
|
196
|
+
configurable: true,
|
|
197
|
+
writable: true,
|
|
198
|
+
value: void 0
|
|
199
|
+
});
|
|
200
|
+
Object.defineProperty(this, "pendingAnimationSnapshot", {
|
|
201
|
+
enumerable: true,
|
|
202
|
+
configurable: true,
|
|
203
|
+
writable: true,
|
|
204
|
+
value: null
|
|
205
|
+
});
|
|
206
|
+
Object.defineProperty(this, "lastSegmentSnapshot", {
|
|
207
|
+
enumerable: true,
|
|
208
|
+
configurable: true,
|
|
209
|
+
writable: true,
|
|
210
|
+
value: new Map()
|
|
211
|
+
});
|
|
212
|
+
this.animation = animation;
|
|
213
|
+
this.nextRenderAnimationMode = animation.show ? 'initial' : 'none';
|
|
214
|
+
}
|
|
215
|
+
prepareForUpdate() {
|
|
216
|
+
if (!this.animation.show ||
|
|
217
|
+
!this.hasRenderedLive ||
|
|
218
|
+
this.animation.duration === 0) {
|
|
219
|
+
this.pendingAnimationSnapshot = null;
|
|
220
|
+
this.nextRenderAnimationMode = 'none';
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.pendingAnimationSnapshot = new Map(this.lastSegmentSnapshot);
|
|
224
|
+
this.nextRenderAnimationMode = 'update';
|
|
225
|
+
}
|
|
226
|
+
getAnimationContext() {
|
|
227
|
+
if (!this.animation.show ||
|
|
228
|
+
this.animation.duration === 0 ||
|
|
229
|
+
this.nextRenderAnimationMode === 'none') {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
mode: this.nextRenderAnimationMode,
|
|
234
|
+
duration: this.animation.duration,
|
|
235
|
+
easing: this.animation.easing,
|
|
236
|
+
previousSnapshot: this.pendingAnimationSnapshot ?? undefined,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
completeRender(pieData, transitions) {
|
|
240
|
+
this.lastSegmentSnapshot =
|
|
241
|
+
createRadialSegmentSnapshotCollection(pieData);
|
|
242
|
+
this.hasRenderedLive = true;
|
|
243
|
+
this.nextRenderAnimationMode = 'none';
|
|
244
|
+
this.pendingAnimationSnapshot = null;
|
|
245
|
+
if (transitions.length === 0) {
|
|
246
|
+
return Promise.resolve();
|
|
247
|
+
}
|
|
248
|
+
return Promise.allSettled(transitions).then(() => undefined);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
export function buildRadialSegmentKeys(pieData) {
|
|
252
|
+
const labelCounts = new Map();
|
|
253
|
+
return pieData.map((datum) => {
|
|
254
|
+
const baseKey = datum.data.label;
|
|
255
|
+
const occurrenceIndex = labelCounts.get(baseKey) ?? 0;
|
|
256
|
+
labelCounts.set(baseKey, occurrenceIndex + 1);
|
|
257
|
+
return occurrenceIndex === 0
|
|
258
|
+
? baseKey
|
|
259
|
+
: `${baseKey}::${occurrenceIndex}`;
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
export function buildRadialAnimatedArcData(pieData, allSegments, context, shape, exitTargetPieData = pieData) {
|
|
263
|
+
const segmentKeys = buildRadialSegmentKeys(pieData);
|
|
264
|
+
const exitTargetByKey = buildRadialDatumByKey(exitTargetPieData);
|
|
265
|
+
const currentKeys = new Set(segmentKeys);
|
|
266
|
+
const collapsedShape = createCollapsedRadialArcShape(shape);
|
|
267
|
+
const animatedData = pieData.map((datum, index) => {
|
|
268
|
+
const key = segmentKeys[index];
|
|
269
|
+
const previous = context?.previousSnapshot?.get(key);
|
|
270
|
+
const startDatum = previous
|
|
271
|
+
? createRadialDatumFromSnapshot(datum.data, previous)
|
|
272
|
+
: context
|
|
273
|
+
? createCollapsedRadialDatum(datum)
|
|
274
|
+
: datum;
|
|
275
|
+
return {
|
|
276
|
+
key,
|
|
277
|
+
datum,
|
|
278
|
+
startDatum,
|
|
279
|
+
endDatum: datum,
|
|
280
|
+
startShape: context && !previous ? collapsedShape : shape,
|
|
281
|
+
endShape: shape,
|
|
282
|
+
initialOpacity: context && !previous ? 0 : 1,
|
|
283
|
+
finalOpacity: 1,
|
|
284
|
+
interactive: true,
|
|
285
|
+
};
|
|
286
|
+
});
|
|
287
|
+
if (!context?.previousSnapshot) {
|
|
288
|
+
return animatedData;
|
|
289
|
+
}
|
|
290
|
+
context.previousSnapshot.forEach((snapshot, key) => {
|
|
291
|
+
if (currentKeys.has(key)) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const exitTargetDatum = exitTargetByKey.get(key);
|
|
295
|
+
const segment = exitTargetDatum?.data ?? findSegmentBySnapshotKey(allSegments, key);
|
|
296
|
+
if (!segment) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const startDatum = createRadialDatumFromSnapshot(segment, snapshot);
|
|
300
|
+
animatedData.push({
|
|
301
|
+
key,
|
|
302
|
+
datum: startDatum,
|
|
303
|
+
startDatum,
|
|
304
|
+
endDatum: createCollapsedRadialDatum(exitTargetDatum ?? startDatum),
|
|
305
|
+
startShape: shape,
|
|
306
|
+
endShape: collapsedShape,
|
|
307
|
+
initialOpacity: 1,
|
|
308
|
+
finalOpacity: 0,
|
|
309
|
+
interactive: false,
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
return animatedData;
|
|
313
|
+
}
|
|
314
|
+
export function buildRadialExitTargetPieData(pieGenerator, visibleSegments, allSegments, context, currentPieData) {
|
|
315
|
+
if (!context?.previousSnapshot) {
|
|
316
|
+
return currentPieData;
|
|
317
|
+
}
|
|
318
|
+
const visibleSegmentSet = new Set(visibleSegments);
|
|
319
|
+
const exitTargetSegments = allSegments.map((segment) => {
|
|
320
|
+
if (visibleSegmentSet.has(segment)) {
|
|
321
|
+
return segment;
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
...segment,
|
|
325
|
+
value: 0,
|
|
326
|
+
};
|
|
327
|
+
});
|
|
328
|
+
return pieGenerator(exitTargetSegments);
|
|
329
|
+
}
|
|
330
|
+
export function renderRadialArcDatum(datum, shape) {
|
|
331
|
+
return (arc()
|
|
332
|
+
.innerRadius(shape.innerRadius)
|
|
333
|
+
.outerRadius(shape.outerRadius)
|
|
334
|
+
.cornerRadius(shape.cornerRadius)(datum) ?? '');
|
|
335
|
+
}
|
|
336
|
+
export function interpolateRadialArcDatum(startDatum, endDatum) {
|
|
337
|
+
const startAngle = interpolateNumber(startDatum.startAngle, endDatum.startAngle);
|
|
338
|
+
const endAngle = interpolateNumber(startDatum.endAngle, endDatum.endAngle);
|
|
339
|
+
const padAngle = interpolateNumber(startDatum.padAngle, endDatum.padAngle);
|
|
340
|
+
const value = interpolateNumber(startDatum.value, endDatum.value);
|
|
341
|
+
return (progress) => {
|
|
342
|
+
return {
|
|
343
|
+
...endDatum,
|
|
344
|
+
startAngle: startAngle(progress),
|
|
345
|
+
endAngle: endAngle(progress),
|
|
346
|
+
padAngle: padAngle(progress),
|
|
347
|
+
value: value(progress),
|
|
348
|
+
};
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
export function interpolateRadialArcShape(startShape, endShape) {
|
|
352
|
+
const innerRadius = interpolateNumber(startShape.innerRadius, endShape.innerRadius);
|
|
353
|
+
const outerRadius = interpolateNumber(startShape.outerRadius, endShape.outerRadius);
|
|
354
|
+
const cornerRadius = interpolateNumber(startShape.cornerRadius, endShape.cornerRadius);
|
|
355
|
+
return (progress) => {
|
|
356
|
+
return {
|
|
357
|
+
innerRadius: innerRadius(progress),
|
|
358
|
+
outerRadius: outerRadius(progress),
|
|
359
|
+
cornerRadius: cornerRadius(progress),
|
|
360
|
+
};
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
export function createTransitionCompletionPromise(transition) {
|
|
364
|
+
return transition.end().then(() => undefined, () => undefined);
|
|
365
|
+
}
|
|
366
|
+
function createCollapsedRadialDatum(datum) {
|
|
367
|
+
const midAngle = (datum.startAngle + datum.endAngle) / 2;
|
|
368
|
+
return {
|
|
369
|
+
...datum,
|
|
370
|
+
startAngle: midAngle,
|
|
371
|
+
endAngle: midAngle,
|
|
372
|
+
padAngle: 0,
|
|
373
|
+
value: 0,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function createCollapsedRadialArcShape(shape) {
|
|
377
|
+
return {
|
|
378
|
+
innerRadius: shape.innerRadius,
|
|
379
|
+
outerRadius: shape.outerRadius,
|
|
380
|
+
cornerRadius: 0,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function createRadialDatumFromSnapshot(data, snapshot) {
|
|
384
|
+
return {
|
|
385
|
+
data,
|
|
386
|
+
value: snapshot.value,
|
|
387
|
+
index: snapshot.index,
|
|
388
|
+
startAngle: snapshot.startAngle,
|
|
389
|
+
endAngle: snapshot.endAngle,
|
|
390
|
+
padAngle: snapshot.padAngle,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function findSegmentBySnapshotKey(segments, key) {
|
|
394
|
+
const [label] = key.split('::');
|
|
395
|
+
return segments.find((segment) => segment.label === label);
|
|
396
|
+
}
|
|
397
|
+
function buildRadialDatumByKey(pieData) {
|
|
398
|
+
return new Map(buildRadialSegmentKeys(pieData).map((key, index) => {
|
|
399
|
+
return [key, pieData[index]];
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
function createRadialSegmentSnapshotCollection(pieData) {
|
|
403
|
+
const segmentKeys = buildRadialSegmentKeys(pieData);
|
|
404
|
+
return new Map(pieData.map((datum, index) => {
|
|
405
|
+
return [
|
|
406
|
+
segmentKeys[index],
|
|
407
|
+
{
|
|
408
|
+
startAngle: datum.startAngle,
|
|
409
|
+
endAngle: datum.endAngle,
|
|
410
|
+
padAngle: datum.padAngle,
|
|
411
|
+
value: datum.value,
|
|
412
|
+
index: datum.index,
|
|
413
|
+
},
|
|
414
|
+
];
|
|
415
|
+
}));
|
|
416
|
+
}
|
|
@@ -1,10 +1,23 @@
|
|
|
1
|
+
import type { BaseType, Selection } from 'd3';
|
|
1
2
|
import { BaseChart } from './base-chart.js';
|
|
2
3
|
import type { PlotAreaBounds } from './layout-manager.js';
|
|
3
|
-
import type { LegendSeries } from './types.js';
|
|
4
|
+
import type { LabelOversizedBehavior, LegendSeries } from './types.js';
|
|
4
5
|
type RadialLegendItem = {
|
|
5
6
|
label: string;
|
|
6
7
|
color: string;
|
|
7
8
|
};
|
|
9
|
+
type RadialLabelOverflowOptions = {
|
|
10
|
+
maxLabelWidth?: number;
|
|
11
|
+
oversizedBehavior: LabelOversizedBehavior;
|
|
12
|
+
fontSize: number | string;
|
|
13
|
+
fontFamily: string;
|
|
14
|
+
fontWeight: number | string;
|
|
15
|
+
forceVisible?: boolean;
|
|
16
|
+
};
|
|
17
|
+
type RadialLabelDimensions = {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
8
21
|
export declare abstract class RadialChartBase extends BaseChart {
|
|
9
22
|
protected initializeTooltip(): void;
|
|
10
23
|
protected getVisibleRadialItems<T extends RadialLegendItem>(items: T[]): T[];
|
|
@@ -16,11 +29,21 @@ export declare abstract class RadialChartBase extends BaseChart {
|
|
|
16
29
|
fontScale: number;
|
|
17
30
|
};
|
|
18
31
|
protected getRadialLegendSeries<T extends RadialLegendItem>(items: T[]): LegendSeries[];
|
|
32
|
+
protected renderRadialLabelText<Datum, ParentElement extends BaseType, ParentDatum>(textElement: Selection<SVGTextElement, Datum, ParentElement, ParentDatum>, text: string, options: RadialLabelOverflowOptions, verticalAnchor?: 'middle' | 'baseline'): void;
|
|
33
|
+
protected renderRadialStructuredLabelText<Datum, ParentElement extends BaseType, ParentDatum>(textElement: Selection<SVGTextElement, Datum, ParentElement, ParentDatum>, labelText: string, valueText: string, separator: string, options: RadialLabelOverflowOptions, verticalAnchor?: 'middle' | 'baseline'): void;
|
|
34
|
+
protected measureRadialLabelDimensions(text: string, options: RadialLabelOverflowOptions): RadialLabelDimensions;
|
|
35
|
+
protected measureRadialStructuredLabelDimensions(labelText: string, valueText: string, separator: string, options: RadialLabelOverflowOptions): RadialLabelDimensions;
|
|
36
|
+
protected resolveRadialLabelLineHeight(fontSize: number | string): number;
|
|
19
37
|
protected showTooltipFromPointer(event: MouseEvent, content: string): void;
|
|
20
38
|
protected positionTooltipFromPointer(event: MouseEvent): void;
|
|
21
39
|
protected showTooltipAtElement(target: Element, content: string): void;
|
|
22
40
|
protected hideTooltip(): void;
|
|
23
41
|
private applyTooltipPosition;
|
|
42
|
+
private resolveRadialLabelLayout;
|
|
43
|
+
private resolveRadialLabelOverflowContext;
|
|
44
|
+
private createVisibleRadialLabelLayout;
|
|
45
|
+
private resolveOverflowingRadialLabelLayout;
|
|
46
|
+
private addRadialLabelTitle;
|
|
24
47
|
private resolveRadialFontScale;
|
|
25
48
|
}
|
|
26
49
|
export {};
|