@internetstiftelsen/charts 0.13.3 → 0.14.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.
- package/README.md +7 -1
- package/dist/area.js +2 -1
- package/dist/bar.js +8 -4
- package/dist/base-chart.d.ts +8 -0
- package/dist/base-chart.js +79 -1
- package/dist/chart-group.d.ts +1 -0
- package/dist/chart-group.js +45 -4
- 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/types.d.ts +1 -0
- package/dist/word-cloud-chart.d.ts +30 -0
- package/dist/word-cloud-chart.js +207 -7
- 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/getting-started.md +3 -0
- package/docs/pie-chart.md +58 -16
- package/docs/theming.md +17 -12
- package/docs/word-cloud-chart.md +43 -8
- package/docs/xy-chart.md +10 -0
- package/package.json +26 -26
package/dist/pie-chart.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { arc, pie, select } from 'd3';
|
|
2
2
|
import { ChartValidator } from './validation.js';
|
|
3
|
-
import { getContrastTextColor,
|
|
3
|
+
import { getContrastTextColor, sanitizeForCSS } from './utils.js';
|
|
4
4
|
import { RadialChartBase } from './radial-chart-base.js';
|
|
5
|
+
import { buildRadialAnimatedArcData, buildRadialExitTargetPieData, createTransitionCompletionPromise, interpolateRadialArcDatum, interpolateRadialArcShape, normalizeRadialAnimationConfig, renderRadialArcDatum, RadialMotionController, } from './radial-animation.js';
|
|
5
6
|
const HOVER_EXPAND_PX = 8;
|
|
6
|
-
const
|
|
7
|
+
const HOVER_ANIMATION_DURATION_MS = 150;
|
|
7
8
|
const FULL_CIRCLE_RADIANS = Math.PI * 2;
|
|
8
9
|
const OUTSIDE_LABEL_TEXT_OFFSET_PX = 10;
|
|
9
10
|
const OUTSIDE_LABEL_LINE_INSET_PX = 4;
|
|
@@ -15,9 +16,9 @@ const DEFAULT_PIE_VALUE_LABEL = {
|
|
|
15
16
|
outsideOffset: 16,
|
|
16
17
|
insideMargin: 8,
|
|
17
18
|
minVerticalSpacing: 14,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
oversizedBehavior: 'truncate',
|
|
20
|
+
forceVisible: false,
|
|
21
|
+
separator: ': ',
|
|
21
22
|
};
|
|
22
23
|
export class PieChart extends RadialChartBase {
|
|
23
24
|
constructor(config) {
|
|
@@ -76,6 +77,12 @@ export class PieChart extends RadialChartBase {
|
|
|
76
77
|
writable: true,
|
|
77
78
|
value: void 0
|
|
78
79
|
});
|
|
80
|
+
Object.defineProperty(this, "motionController", {
|
|
81
|
+
enumerable: true,
|
|
82
|
+
configurable: true,
|
|
83
|
+
writable: true,
|
|
84
|
+
value: void 0
|
|
85
|
+
});
|
|
79
86
|
Object.defineProperty(this, "segments", {
|
|
80
87
|
enumerable: true,
|
|
81
88
|
configurable: true,
|
|
@@ -108,6 +115,7 @@ export class PieChart extends RadialChartBase {
|
|
|
108
115
|
...DEFAULT_PIE_VALUE_LABEL,
|
|
109
116
|
...config.valueLabel,
|
|
110
117
|
};
|
|
118
|
+
this.motionController = new RadialMotionController(normalizeRadialAnimationConfig(config.animate, 'PieChart'));
|
|
111
119
|
this.initializeDataState();
|
|
112
120
|
}
|
|
113
121
|
validatePieData() {
|
|
@@ -203,10 +211,36 @@ export class PieChart extends RadialChartBase {
|
|
|
203
211
|
});
|
|
204
212
|
}
|
|
205
213
|
update(data) {
|
|
214
|
+
this.motionController.prepareForUpdate();
|
|
206
215
|
super.update(data);
|
|
207
216
|
}
|
|
208
|
-
|
|
209
|
-
|
|
217
|
+
prepareForLegendChange() {
|
|
218
|
+
this.motionController.prepareForUpdate();
|
|
219
|
+
}
|
|
220
|
+
resolveValueLabelText(segment, total) {
|
|
221
|
+
const percentage = this.getValueLabelPercentage(segment, total);
|
|
222
|
+
if (this.valueLabel.formatter) {
|
|
223
|
+
const fullText = this.valueLabel.formatter(segment.label, segment.value, segment.source, percentage);
|
|
224
|
+
return {
|
|
225
|
+
label: fullText,
|
|
226
|
+
value: '',
|
|
227
|
+
separator: '',
|
|
228
|
+
fullText,
|
|
229
|
+
isCustom: true,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const label = this.valueLabel.labelFormatter?.(segment.label, segment.value, segment.source, percentage) ?? segment.label;
|
|
233
|
+
const value = this.valueLabel.valueFormatter?.(segment.label, segment.value, segment.source, percentage) ?? String(segment.value);
|
|
234
|
+
return {
|
|
235
|
+
label,
|
|
236
|
+
value,
|
|
237
|
+
separator: this.valueLabel.separator,
|
|
238
|
+
fullText: `${label}${this.valueLabel.separator}${value}`,
|
|
239
|
+
isCustom: false,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
getValueLabelPercentage(segment, total) {
|
|
243
|
+
return total > 0 ? (segment.value / total) * 100 : 0;
|
|
210
244
|
}
|
|
211
245
|
createExportChart() {
|
|
212
246
|
return new PieChart({
|
|
@@ -224,6 +258,7 @@ export class PieChart extends RadialChartBase {
|
|
|
224
258
|
sort: this.sort,
|
|
225
259
|
},
|
|
226
260
|
valueLabel: this.valueLabel,
|
|
261
|
+
animate: false,
|
|
227
262
|
valueKey: this.valueKey,
|
|
228
263
|
labelKey: this.labelKey,
|
|
229
264
|
});
|
|
@@ -235,16 +270,16 @@ export class PieChart extends RadialChartBase {
|
|
|
235
270
|
renderChart({ svg, plotGroup, plotArea, }) {
|
|
236
271
|
this.renderTitle(svg);
|
|
237
272
|
const visibleSegments = this.getVisibleRadialItems(this.segments);
|
|
273
|
+
const animationContext = this.motionController.getAnimationContext();
|
|
274
|
+
const { cx, cy, outerRadius, innerRadius, fontScale } = this.getRadialLayout(plotArea, this.innerRadiusRatio);
|
|
238
275
|
this.initializeTooltip();
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return sum + segment.value;
|
|
245
|
-
}, 0), fontScale);
|
|
246
|
-
}
|
|
276
|
+
const { segmentGroup, pieData, transitions } = this.renderSegments(plotGroup, visibleSegments, cx, cy, innerRadius, outerRadius, animationContext, this.segments);
|
|
277
|
+
if (this.valueLabel.show && visibleSegments.length > 0) {
|
|
278
|
+
this.renderLabels(segmentGroup, pieData, innerRadius, outerRadius, visibleSegments.reduce((sum, segment) => {
|
|
279
|
+
return sum + segment.value;
|
|
280
|
+
}, 0), fontScale);
|
|
247
281
|
}
|
|
282
|
+
this.setReadyPromise(this.motionController.completeRender(pieData, transitions));
|
|
248
283
|
this.renderInlineLegend(svg);
|
|
249
284
|
}
|
|
250
285
|
getLegendSeries() {
|
|
@@ -262,7 +297,7 @@ export class PieChart extends RadialChartBase {
|
|
|
262
297
|
}
|
|
263
298
|
return null;
|
|
264
299
|
}
|
|
265
|
-
renderSegments(plotGroup, segments, cx, cy, innerRadius, outerRadius) {
|
|
300
|
+
renderSegments(plotGroup, segments, cx, cy, innerRadius, outerRadius, animationContext, allSegments) {
|
|
266
301
|
const pieGenerator = pie()
|
|
267
302
|
.value((d) => d.value)
|
|
268
303
|
.startAngle(this.startAngle)
|
|
@@ -284,30 +319,72 @@ export class PieChart extends RadialChartBase {
|
|
|
284
319
|
.outerRadius(outerRadius + HOVER_EXPAND_PX)
|
|
285
320
|
.cornerRadius(this.cornerRadius);
|
|
286
321
|
const pieData = pieGenerator(segments);
|
|
322
|
+
const exitTargetPieData = buildRadialExitTargetPieData(pieGenerator, segments, allSegments, animationContext, pieData);
|
|
323
|
+
const arcShape = {
|
|
324
|
+
innerRadius,
|
|
325
|
+
outerRadius,
|
|
326
|
+
cornerRadius: this.cornerRadius,
|
|
327
|
+
};
|
|
287
328
|
const segmentGroup = plotGroup
|
|
288
329
|
.append('g')
|
|
289
330
|
.attr('class', 'pie-segments')
|
|
290
331
|
.attr('transform', `translate(${cx}, ${cy})`);
|
|
291
332
|
const total = segments.reduce((sum, segment) => sum + segment.value, 0);
|
|
333
|
+
const animatedArcData = buildRadialAnimatedArcData(pieData, allSegments, animationContext, arcShape, exitTargetPieData);
|
|
334
|
+
const animationByDatum = new Map(animatedArcData.map((entry) => [entry.datum, entry]));
|
|
335
|
+
const getAnimation = (datum) => {
|
|
336
|
+
return animationByDatum.get(datum);
|
|
337
|
+
};
|
|
338
|
+
const renderedArcData = animatedArcData.map((entry) => entry.datum);
|
|
339
|
+
const transitions = [];
|
|
292
340
|
const segmentSelection = segmentGroup
|
|
293
341
|
.selectAll('.pie-segment')
|
|
294
|
-
.data(
|
|
342
|
+
.data(renderedArcData, (d) => getAnimation(d).key)
|
|
295
343
|
.join('path')
|
|
296
344
|
.attr('class', (d) => `pie-segment segment-${sanitizeForCSS(d.data.label)}`)
|
|
297
|
-
.attr('d',
|
|
345
|
+
.attr('d', (d) => {
|
|
346
|
+
const animation = getAnimation(d);
|
|
347
|
+
return renderRadialArcDatum(animation.startDatum, animation.startShape);
|
|
348
|
+
})
|
|
298
349
|
.attr('fill', (d) => d.data.color)
|
|
299
|
-
.attr('
|
|
300
|
-
.attr('
|
|
301
|
-
.
|
|
350
|
+
.attr('opacity', (d) => getAnimation(d).initialOpacity)
|
|
351
|
+
.attr('tabindex', (d) => (getAnimation(d).interactive ? 0 : null))
|
|
352
|
+
.attr('aria-hidden', (d) => getAnimation(d).interactive ? null : 'true')
|
|
353
|
+
.attr('aria-label', (d) => {
|
|
354
|
+
return getAnimation(d).interactive
|
|
355
|
+
? this.buildAriaLabel(d, total)
|
|
356
|
+
: null;
|
|
357
|
+
})
|
|
358
|
+
.style('cursor', (d) => getAnimation(d).interactive ? 'pointer' : 'default')
|
|
359
|
+
.style('pointer-events', (d) => getAnimation(d).interactive ? 'all' : 'none')
|
|
302
360
|
.style('transition', 'opacity 0.15s ease');
|
|
303
|
-
|
|
361
|
+
if (animationContext) {
|
|
362
|
+
const transition = segmentSelection
|
|
363
|
+
.transition()
|
|
364
|
+
.duration(animationContext.duration)
|
|
365
|
+
.ease(animationContext.easing)
|
|
366
|
+
.attrTween('d', (d) => {
|
|
367
|
+
const animation = getAnimation(d);
|
|
368
|
+
const datumInterpolator = interpolateRadialArcDatum(animation.startDatum, animation.endDatum);
|
|
369
|
+
const shapeInterpolator = interpolateRadialArcShape(animation.startShape, animation.endShape);
|
|
370
|
+
return (progress) => {
|
|
371
|
+
return renderRadialArcDatum(datumInterpolator(progress), shapeInterpolator(progress));
|
|
372
|
+
};
|
|
373
|
+
})
|
|
374
|
+
.attr('opacity', (d) => getAnimation(d).finalOpacity);
|
|
375
|
+
transitions.push(createTransitionCompletionPromise(transition));
|
|
376
|
+
}
|
|
377
|
+
const interactiveSegmentSelection = segmentSelection.filter((d) => {
|
|
378
|
+
return getAnimation(d).interactive;
|
|
379
|
+
});
|
|
380
|
+
interactiveSegmentSelection
|
|
304
381
|
.on('mouseenter', (event, d) => {
|
|
305
382
|
const target = event.currentTarget;
|
|
306
383
|
select(target)
|
|
307
384
|
.transition()
|
|
308
|
-
.duration(
|
|
385
|
+
.duration(HOVER_ANIMATION_DURATION_MS)
|
|
309
386
|
.attr('d', hoverArcGenerator(d));
|
|
310
|
-
|
|
387
|
+
interactiveSegmentSelection
|
|
311
388
|
.filter((_, i, nodes) => nodes[i] !== target)
|
|
312
389
|
.style('opacity', 0.5);
|
|
313
390
|
this.showTooltipFromPointer(event, this.buildTooltipContent(d, segments));
|
|
@@ -319,18 +396,18 @@ export class PieChart extends RadialChartBase {
|
|
|
319
396
|
const target = event.currentTarget;
|
|
320
397
|
select(target)
|
|
321
398
|
.transition()
|
|
322
|
-
.duration(
|
|
399
|
+
.duration(HOVER_ANIMATION_DURATION_MS)
|
|
323
400
|
.attr('d', arcGenerator(d));
|
|
324
|
-
|
|
401
|
+
interactiveSegmentSelection.style('opacity', 1);
|
|
325
402
|
this.hideTooltip();
|
|
326
403
|
})
|
|
327
404
|
.on('focus', (event, d) => {
|
|
328
405
|
const target = event.currentTarget;
|
|
329
406
|
select(target)
|
|
330
407
|
.transition()
|
|
331
|
-
.duration(
|
|
408
|
+
.duration(HOVER_ANIMATION_DURATION_MS)
|
|
332
409
|
.attr('d', hoverArcGenerator(d));
|
|
333
|
-
|
|
410
|
+
interactiveSegmentSelection
|
|
334
411
|
.filter((_, i, nodes) => nodes[i] !== target)
|
|
335
412
|
.style('opacity', 0.5);
|
|
336
413
|
this.showTooltipAtElement(target, this.buildTooltipContent(d, segments));
|
|
@@ -339,9 +416,9 @@ export class PieChart extends RadialChartBase {
|
|
|
339
416
|
const target = event.currentTarget;
|
|
340
417
|
select(target)
|
|
341
418
|
.transition()
|
|
342
|
-
.duration(
|
|
419
|
+
.duration(HOVER_ANIMATION_DURATION_MS)
|
|
343
420
|
.attr('d', arcGenerator(d));
|
|
344
|
-
|
|
421
|
+
interactiveSegmentSelection.style('opacity', 1);
|
|
345
422
|
this.hideTooltip();
|
|
346
423
|
})
|
|
347
424
|
.on('keydown', (event) => {
|
|
@@ -350,6 +427,7 @@ export class PieChart extends RadialChartBase {
|
|
|
350
427
|
return {
|
|
351
428
|
segmentGroup,
|
|
352
429
|
pieData,
|
|
430
|
+
transitions,
|
|
353
431
|
};
|
|
354
432
|
}
|
|
355
433
|
handleSegmentKeyNavigation(event) {
|
|
@@ -415,30 +493,44 @@ export class PieChart extends RadialChartBase {
|
|
|
415
493
|
.innerRadius(insideLabelRadius)
|
|
416
494
|
.outerRadius(insideLabelRadius);
|
|
417
495
|
const fontSize = this.renderTheme.legend.fontSize * fontScale;
|
|
496
|
+
const fontFamily = this.renderTheme.axis.fontFamily;
|
|
418
497
|
const fontWeight = this.renderTheme.axis.fontWeight ?? 'normal';
|
|
498
|
+
const labelOverflowOptions = {
|
|
499
|
+
maxLabelWidth: this.valueLabel.maxLabelWidth,
|
|
500
|
+
oversizedBehavior: this.valueLabel.oversizedBehavior,
|
|
501
|
+
forceVisible: this.valueLabel.forceVisible,
|
|
502
|
+
fontSize,
|
|
503
|
+
fontFamily,
|
|
504
|
+
fontWeight,
|
|
505
|
+
};
|
|
419
506
|
const outsideLabels = [];
|
|
420
507
|
pieData.forEach((d) => {
|
|
421
508
|
const percentage = total > 0 ? (d.data.value / total) * 100 : 0;
|
|
422
|
-
const
|
|
423
|
-
const
|
|
509
|
+
const valueLabel = this.resolveValueLabelText(d.data, total);
|
|
510
|
+
const labelDimensions = this.measureValueLabelDimensions(valueLabel, labelOverflowOptions);
|
|
511
|
+
const placement = this.resolveValueLabelPlacement(d, labelDimensions, percentage, innerRadius, outerRadius, insideLabelRadius);
|
|
424
512
|
if (placement === 'inside') {
|
|
425
513
|
const [x, y] = insideArc.centroid(d);
|
|
426
|
-
labelGroup
|
|
514
|
+
const textElement = labelGroup
|
|
427
515
|
.append('text')
|
|
428
516
|
.attr('class', `pie-label pie-label--inside pie-label-${sanitizeForCSS(d.data.label)}`)
|
|
429
517
|
.attr('x', x)
|
|
430
518
|
.attr('y', y)
|
|
431
519
|
.attr('text-anchor', 'middle')
|
|
432
520
|
.attr('dominant-baseline', 'middle')
|
|
433
|
-
.attr('font-family',
|
|
521
|
+
.attr('font-family', fontFamily)
|
|
434
522
|
.attr('font-size', `${fontSize}px`)
|
|
435
523
|
.attr('font-weight', fontWeight)
|
|
436
|
-
.attr('fill', getContrastTextColor(d.data.color))
|
|
437
|
-
|
|
524
|
+
.attr('fill', getContrastTextColor(d.data.color));
|
|
525
|
+
if (valueLabel.isCustom) {
|
|
526
|
+
this.renderRadialLabelText(textElement, valueLabel.fullText, labelOverflowOptions);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
this.renderRadialStructuredLabelText(textElement, valueLabel.label, valueLabel.value, valueLabel.separator, labelOverflowOptions);
|
|
438
530
|
return;
|
|
439
531
|
}
|
|
440
532
|
if (placement === 'outside') {
|
|
441
|
-
outsideLabels.push(this.resolveOutsideLabel(d, outerRadius));
|
|
533
|
+
outsideLabels.push(this.resolveOutsideLabel(d, outerRadius, valueLabel, labelDimensions.height));
|
|
442
534
|
}
|
|
443
535
|
});
|
|
444
536
|
if (outsideLabels.length === 0) {
|
|
@@ -468,24 +560,30 @@ export class PieChart extends RadialChartBase {
|
|
|
468
560
|
.attr('fill', 'none')
|
|
469
561
|
.attr('stroke', '#9ca3af')
|
|
470
562
|
.attr('stroke-width', 1);
|
|
471
|
-
labelGroup
|
|
563
|
+
const textElement = labelGroup
|
|
472
564
|
.append('text')
|
|
473
565
|
.attr('class', `pie-label pie-label--outside pie-label-${sanitizeForCSS(outsideLabel.datum.data.label)}`)
|
|
474
566
|
.attr('x', textX)
|
|
475
567
|
.attr('y', outsideLabel.y)
|
|
476
568
|
.attr('text-anchor', outsideLabel.textAnchor)
|
|
477
569
|
.attr('dominant-baseline', 'middle')
|
|
478
|
-
.attr('font-family',
|
|
570
|
+
.attr('font-family', fontFamily)
|
|
479
571
|
.attr('font-size', `${fontSize}px`)
|
|
480
572
|
.attr('font-weight', fontWeight)
|
|
481
|
-
.attr('fill', this.renderTheme.valueLabel.color)
|
|
482
|
-
|
|
573
|
+
.attr('fill', this.renderTheme.valueLabel.color);
|
|
574
|
+
if (outsideLabel.valueLabel.isCustom) {
|
|
575
|
+
this.renderRadialLabelText(textElement, outsideLabel.valueLabel.fullText, labelOverflowOptions);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
this.renderRadialStructuredLabelText(textElement, outsideLabel.valueLabel.label, outsideLabel.valueLabel.value, outsideLabel.valueLabel.separator, labelOverflowOptions);
|
|
483
579
|
});
|
|
484
580
|
}
|
|
485
|
-
resolveValueLabelPlacement(datum,
|
|
486
|
-
const fitsInside = this.canFitInsideLabel(datum,
|
|
581
|
+
resolveValueLabelPlacement(datum, labelDimensions, percentage, innerRadius, outerRadius, insideLabelRadius) {
|
|
582
|
+
const fitsInside = this.canFitInsideLabel(datum, labelDimensions, innerRadius, outerRadius, insideLabelRadius);
|
|
487
583
|
if (this.valueLabel.position === 'inside') {
|
|
488
|
-
return fitsInside
|
|
584
|
+
return fitsInside || this.valueLabel.forceVisible
|
|
585
|
+
? 'inside'
|
|
586
|
+
: 'hidden';
|
|
489
587
|
}
|
|
490
588
|
if (this.valueLabel.position === 'outside') {
|
|
491
589
|
return 'outside';
|
|
@@ -498,23 +596,14 @@ export class PieChart extends RadialChartBase {
|
|
|
498
596
|
}
|
|
499
597
|
return 'inside';
|
|
500
598
|
}
|
|
501
|
-
canFitInsideLabel(datum,
|
|
502
|
-
if (!this.svg) {
|
|
503
|
-
return false;
|
|
504
|
-
}
|
|
505
|
-
const svgNode = this.svg.node();
|
|
506
|
-
if (!svgNode) {
|
|
507
|
-
return false;
|
|
508
|
-
}
|
|
509
|
-
const textWidth = measureTextWidth(labelText, fontSize, this.renderTheme.axis.fontFamily, fontWeight, svgNode);
|
|
599
|
+
canFitInsideLabel(datum, labelDimensions, innerRadius, outerRadius, insideLabelRadius) {
|
|
510
600
|
const angle = Math.max(0, datum.endAngle - datum.startAngle);
|
|
511
601
|
const availableArcLength = angle * insideLabelRadius - this.valueLabel.insideMargin * 2;
|
|
512
602
|
const availableRadialThickness = outerRadius - innerRadius - this.valueLabel.insideMargin * 2;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
availableRadialThickness >= verticalFitThreshold);
|
|
603
|
+
return (availableArcLength >= labelDimensions.width &&
|
|
604
|
+
availableRadialThickness >= labelDimensions.height);
|
|
516
605
|
}
|
|
517
|
-
resolveOutsideLabel(datum, outerRadius) {
|
|
606
|
+
resolveOutsideLabel(datum, outerRadius, valueLabel, height) {
|
|
518
607
|
const midAngle = (datum.startAngle + datum.endAngle) / 2;
|
|
519
608
|
const point = this.getArcPoint(midAngle, outerRadius + this.valueLabel.outsideOffset);
|
|
520
609
|
const side = point.x >= 0 ? 'right' : 'left';
|
|
@@ -523,8 +612,16 @@ export class PieChart extends RadialChartBase {
|
|
|
523
612
|
y: point.y,
|
|
524
613
|
side,
|
|
525
614
|
textAnchor: side === 'right' ? 'start' : 'end',
|
|
615
|
+
valueLabel,
|
|
616
|
+
height,
|
|
526
617
|
};
|
|
527
618
|
}
|
|
619
|
+
measureValueLabelDimensions(valueLabel, labelOverflowOptions) {
|
|
620
|
+
if (valueLabel.isCustom) {
|
|
621
|
+
return this.measureRadialLabelDimensions(valueLabel.fullText, labelOverflowOptions);
|
|
622
|
+
}
|
|
623
|
+
return this.measureRadialStructuredLabelDimensions(valueLabel.label, valueLabel.value, valueLabel.separator, labelOverflowOptions);
|
|
624
|
+
}
|
|
528
625
|
adjustOutsideLabelPositions(labels, outerRadius) {
|
|
529
626
|
const adjustForSide = (side) => {
|
|
530
627
|
const sideLabels = labels
|
|
@@ -537,7 +634,8 @@ export class PieChart extends RadialChartBase {
|
|
|
537
634
|
const bottomLimit = outerRadius;
|
|
538
635
|
sideLabels[0].y = Math.max(topLimit, sideLabels[0].y);
|
|
539
636
|
for (let i = 1; i < sideLabels.length; i++) {
|
|
540
|
-
const minY = sideLabels[i - 1].y +
|
|
637
|
+
const minY = sideLabels[i - 1].y +
|
|
638
|
+
this.getOutsideLabelSpacing(sideLabels[i - 1], sideLabels[i]);
|
|
541
639
|
sideLabels[i].y = Math.max(sideLabels[i].y, minY);
|
|
542
640
|
}
|
|
543
641
|
const overflow = sideLabels[sideLabels.length - 1].y - bottomLimit;
|
|
@@ -545,7 +643,7 @@ export class PieChart extends RadialChartBase {
|
|
|
545
643
|
sideLabels[sideLabels.length - 1].y -= overflow;
|
|
546
644
|
for (let i = sideLabels.length - 2; i >= 0; i--) {
|
|
547
645
|
const maxY = sideLabels[i + 1].y -
|
|
548
|
-
this.
|
|
646
|
+
this.getOutsideLabelSpacing(sideLabels[i], sideLabels[i + 1]);
|
|
549
647
|
sideLabels[i].y = Math.min(sideLabels[i].y, maxY);
|
|
550
648
|
}
|
|
551
649
|
const underflow = topLimit - sideLabels[0].y;
|
|
@@ -564,4 +662,7 @@ export class PieChart extends RadialChartBase {
|
|
|
564
662
|
const leftLabels = adjustForSide('left');
|
|
565
663
|
return [...rightLabels, ...leftLabels];
|
|
566
664
|
}
|
|
665
|
+
getOutsideLabelSpacing(previousLabel, currentLabel) {
|
|
666
|
+
return Math.max(this.valueLabel.minVerticalSpacing, (previousLabel.height + currentLabel.height) / 2);
|
|
667
|
+
}
|
|
567
668
|
}
|
|
@@ -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 {};
|