@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/donut-chart.js
CHANGED
|
@@ -2,8 +2,9 @@ import { arc, pie, select } from 'd3';
|
|
|
2
2
|
import { sanitizeForCSS } from './utils.js';
|
|
3
3
|
import { ChartValidator } from './validation.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 OUTSIDE_LABEL_TEXT_OFFSET_PX = 10;
|
|
8
9
|
const OUTSIDE_LABEL_LINE_INSET_PX = 4;
|
|
9
10
|
const DEFAULT_DONUT_VALUE_LABEL = {
|
|
@@ -11,9 +12,9 @@ const DEFAULT_DONUT_VALUE_LABEL = {
|
|
|
11
12
|
position: 'auto',
|
|
12
13
|
outsideOffset: 16,
|
|
13
14
|
minVerticalSpacing: 14,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
oversizedBehavior: 'truncate',
|
|
16
|
+
forceVisible: false,
|
|
17
|
+
separator: ': ',
|
|
17
18
|
};
|
|
18
19
|
export class DonutChart extends RadialChartBase {
|
|
19
20
|
constructor(config) {
|
|
@@ -54,6 +55,12 @@ export class DonutChart extends RadialChartBase {
|
|
|
54
55
|
writable: true,
|
|
55
56
|
value: void 0
|
|
56
57
|
});
|
|
58
|
+
Object.defineProperty(this, "motionController", {
|
|
59
|
+
enumerable: true,
|
|
60
|
+
configurable: true,
|
|
61
|
+
writable: true,
|
|
62
|
+
value: void 0
|
|
63
|
+
});
|
|
57
64
|
Object.defineProperty(this, "segments", {
|
|
58
65
|
enumerable: true,
|
|
59
66
|
configurable: true,
|
|
@@ -87,6 +94,7 @@ export class DonutChart extends RadialChartBase {
|
|
|
87
94
|
this.valueKey = resolvedConfig.valueKey;
|
|
88
95
|
this.labelKey = resolvedConfig.labelKey;
|
|
89
96
|
this.valueLabel = valueLabel;
|
|
97
|
+
this.motionController = new RadialMotionController(normalizeRadialAnimationConfig(config.animate, 'DonutChart'));
|
|
90
98
|
this.initializeDataState();
|
|
91
99
|
}
|
|
92
100
|
validateDonutData() {
|
|
@@ -142,8 +150,12 @@ export class DonutChart extends RadialChartBase {
|
|
|
142
150
|
];
|
|
143
151
|
}
|
|
144
152
|
update(data) {
|
|
153
|
+
this.motionController.prepareForUpdate();
|
|
145
154
|
super.update(data);
|
|
146
155
|
}
|
|
156
|
+
prepareForLegendChange() {
|
|
157
|
+
this.motionController.prepareForUpdate();
|
|
158
|
+
}
|
|
147
159
|
createExportChart() {
|
|
148
160
|
return new DonutChart({
|
|
149
161
|
data: this.data,
|
|
@@ -157,6 +169,7 @@ export class DonutChart extends RadialChartBase {
|
|
|
157
169
|
cornerRadius: this.cornerRadius,
|
|
158
170
|
},
|
|
159
171
|
valueLabel: this.valueLabel,
|
|
172
|
+
animate: false,
|
|
160
173
|
valueKey: this.valueKey,
|
|
161
174
|
labelKey: this.labelKey,
|
|
162
175
|
});
|
|
@@ -184,9 +197,10 @@ export class DonutChart extends RadialChartBase {
|
|
|
184
197
|
renderChart({ svg, plotGroup, plotArea, }) {
|
|
185
198
|
this.renderTitle(svg);
|
|
186
199
|
const visibleSegments = this.getVisibleRadialItems(this.segments);
|
|
200
|
+
const animationContext = this.motionController.getAnimationContext();
|
|
187
201
|
const { cx, cy, outerRadius, innerRadius, fontScale } = this.getRadialLayout(plotArea, this.innerRadiusRatio);
|
|
188
202
|
this.initializeTooltip();
|
|
189
|
-
const { segmentGroup, pieData } = this.renderSegments(plotGroup, visibleSegments, cx, cy, innerRadius, outerRadius);
|
|
203
|
+
const { segmentGroup, pieData, transitions } = this.renderSegments(plotGroup, visibleSegments, cx, cy, innerRadius, outerRadius, animationContext, this.segments);
|
|
190
204
|
if (this.valueLabel.show && visibleSegments.length > 0) {
|
|
191
205
|
this.renderLabels(segmentGroup, pieData, outerRadius, visibleSegments.reduce((sum, segment) => {
|
|
192
206
|
return sum + segment.value;
|
|
@@ -195,6 +209,7 @@ export class DonutChart extends RadialChartBase {
|
|
|
195
209
|
if (this.centerContent) {
|
|
196
210
|
this.centerContent.render(svg, cx, cy, this.renderTheme, fontScale);
|
|
197
211
|
}
|
|
212
|
+
this.setReadyPromise(this.motionController.completeRender(pieData, transitions));
|
|
198
213
|
this.renderInlineLegend(svg);
|
|
199
214
|
}
|
|
200
215
|
getLegendSeries() {
|
|
@@ -214,10 +229,32 @@ export class DonutChart extends RadialChartBase {
|
|
|
214
229
|
}
|
|
215
230
|
return `<strong>${d.data.label}</strong><br/>${d.data.value} (${percentage}%)`;
|
|
216
231
|
}
|
|
217
|
-
|
|
218
|
-
|
|
232
|
+
resolveValueLabelText(segment, total) {
|
|
233
|
+
const percentage = this.getValueLabelPercentage(segment, total);
|
|
234
|
+
if (this.valueLabel.formatter) {
|
|
235
|
+
const fullText = this.valueLabel.formatter(segment.label, segment.value, segment.source, percentage);
|
|
236
|
+
return {
|
|
237
|
+
label: fullText,
|
|
238
|
+
value: '',
|
|
239
|
+
separator: '',
|
|
240
|
+
fullText,
|
|
241
|
+
isCustom: true,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const label = this.valueLabel.labelFormatter?.(segment.label, segment.value, segment.source, percentage) ?? segment.label;
|
|
245
|
+
const value = this.valueLabel.valueFormatter?.(segment.label, segment.value, segment.source, percentage) ?? String(segment.value);
|
|
246
|
+
return {
|
|
247
|
+
label,
|
|
248
|
+
value,
|
|
249
|
+
separator: this.valueLabel.separator,
|
|
250
|
+
fullText: `${label}${this.valueLabel.separator}${value}`,
|
|
251
|
+
isCustom: false,
|
|
252
|
+
};
|
|
219
253
|
}
|
|
220
|
-
|
|
254
|
+
getValueLabelPercentage(segment, total) {
|
|
255
|
+
return total > 0 ? (segment.value / total) * 100 : 0;
|
|
256
|
+
}
|
|
257
|
+
renderSegments(plotGroup, segments, cx, cy, innerRadius, outerRadius, animationContext, allSegments) {
|
|
221
258
|
const pieGenerator = pie()
|
|
222
259
|
.value((d) => d.value)
|
|
223
260
|
.padAngle(this.padAngle)
|
|
@@ -231,26 +268,64 @@ export class DonutChart extends RadialChartBase {
|
|
|
231
268
|
.outerRadius(outerRadius + HOVER_EXPAND_PX)
|
|
232
269
|
.cornerRadius(this.cornerRadius);
|
|
233
270
|
const pieData = pieGenerator(segments);
|
|
271
|
+
const exitTargetPieData = buildRadialExitTargetPieData(pieGenerator, segments, allSegments, animationContext, pieData);
|
|
272
|
+
const arcShape = {
|
|
273
|
+
innerRadius,
|
|
274
|
+
outerRadius,
|
|
275
|
+
cornerRadius: this.cornerRadius,
|
|
276
|
+
};
|
|
234
277
|
const segmentGroup = plotGroup
|
|
235
278
|
.append('g')
|
|
236
279
|
.attr('class', 'donut-segments')
|
|
237
280
|
.attr('transform', `translate(${cx}, ${cy})`);
|
|
238
|
-
|
|
281
|
+
const animatedArcData = buildRadialAnimatedArcData(pieData, allSegments, animationContext, arcShape, exitTargetPieData);
|
|
282
|
+
const animationByDatum = new Map(animatedArcData.map((entry) => [entry.datum, entry]));
|
|
283
|
+
const getAnimation = (datum) => {
|
|
284
|
+
return animationByDatum.get(datum);
|
|
285
|
+
};
|
|
286
|
+
const renderedArcData = animatedArcData.map((entry) => entry.datum);
|
|
287
|
+
const transitions = [];
|
|
288
|
+
const segmentSelection = segmentGroup
|
|
239
289
|
.selectAll('.donut-segment')
|
|
240
|
-
.data(
|
|
290
|
+
.data(renderedArcData, (d) => getAnimation(d).key)
|
|
241
291
|
.join('path')
|
|
242
292
|
.attr('class', (d) => `donut-segment segment-${sanitizeForCSS(d.data.label)}`)
|
|
243
|
-
.attr('d',
|
|
293
|
+
.attr('d', (d) => {
|
|
294
|
+
const animation = getAnimation(d);
|
|
295
|
+
return renderRadialArcDatum(animation.startDatum, animation.startShape);
|
|
296
|
+
})
|
|
244
297
|
.attr('fill', (d) => d.data.color)
|
|
245
|
-
.
|
|
246
|
-
.
|
|
298
|
+
.attr('opacity', (d) => getAnimation(d).initialOpacity)
|
|
299
|
+
.attr('aria-hidden', (d) => getAnimation(d).interactive ? null : 'true')
|
|
300
|
+
.style('cursor', (d) => getAnimation(d).interactive ? 'pointer' : 'default')
|
|
301
|
+
.style('pointer-events', (d) => getAnimation(d).interactive ? 'all' : 'none')
|
|
302
|
+
.style('transition', 'opacity 0.15s ease');
|
|
303
|
+
if (animationContext) {
|
|
304
|
+
const transition = segmentSelection
|
|
305
|
+
.transition()
|
|
306
|
+
.duration(animationContext.duration)
|
|
307
|
+
.ease(animationContext.easing)
|
|
308
|
+
.attrTween('d', (d) => {
|
|
309
|
+
const animation = getAnimation(d);
|
|
310
|
+
const datumInterpolator = interpolateRadialArcDatum(animation.startDatum, animation.endDatum);
|
|
311
|
+
const shapeInterpolator = interpolateRadialArcShape(animation.startShape, animation.endShape);
|
|
312
|
+
return (progress) => {
|
|
313
|
+
return renderRadialArcDatum(datumInterpolator(progress), shapeInterpolator(progress));
|
|
314
|
+
};
|
|
315
|
+
})
|
|
316
|
+
.attr('opacity', (d) => getAnimation(d).finalOpacity);
|
|
317
|
+
transitions.push(createTransitionCompletionPromise(transition));
|
|
318
|
+
}
|
|
319
|
+
const interactiveSegmentSelection = segmentSelection.filter((d) => {
|
|
320
|
+
return getAnimation(d).interactive;
|
|
321
|
+
});
|
|
322
|
+
interactiveSegmentSelection
|
|
247
323
|
.on('mouseenter', (event, d) => {
|
|
248
324
|
select(event.currentTarget)
|
|
249
325
|
.transition()
|
|
250
|
-
.duration(
|
|
326
|
+
.duration(HOVER_ANIMATION_DURATION_MS)
|
|
251
327
|
.attr('d', hoverArcGenerator(d));
|
|
252
|
-
|
|
253
|
-
.selectAll('.donut-segment')
|
|
328
|
+
interactiveSegmentSelection
|
|
254
329
|
.filter((_, i, nodes) => nodes[i] !== event.currentTarget)
|
|
255
330
|
.style('opacity', 0.5);
|
|
256
331
|
this.showTooltipFromPointer(event, this.buildTooltipContent(d, segments));
|
|
@@ -261,14 +336,15 @@ export class DonutChart extends RadialChartBase {
|
|
|
261
336
|
.on('mouseleave', (event, d) => {
|
|
262
337
|
select(event.currentTarget)
|
|
263
338
|
.transition()
|
|
264
|
-
.duration(
|
|
339
|
+
.duration(HOVER_ANIMATION_DURATION_MS)
|
|
265
340
|
.attr('d', arcGenerator(d));
|
|
266
|
-
|
|
341
|
+
interactiveSegmentSelection.style('opacity', 1);
|
|
267
342
|
this.hideTooltip();
|
|
268
343
|
});
|
|
269
344
|
return {
|
|
270
345
|
segmentGroup,
|
|
271
346
|
pieData,
|
|
347
|
+
transitions,
|
|
272
348
|
};
|
|
273
349
|
}
|
|
274
350
|
renderLabels(segmentGroup, pieData, outerRadius, total, fontScale) {
|
|
@@ -281,7 +357,19 @@ export class DonutChart extends RadialChartBase {
|
|
|
281
357
|
const fontSize = this.renderTheme.valueLabel.fontSize * fontScale;
|
|
282
358
|
const fontFamily = this.renderTheme.valueLabel.fontFamily;
|
|
283
359
|
const fontWeight = this.renderTheme.valueLabel.fontWeight;
|
|
284
|
-
const
|
|
360
|
+
const labelOverflowOptions = {
|
|
361
|
+
maxLabelWidth: this.valueLabel.maxLabelWidth,
|
|
362
|
+
oversizedBehavior: this.valueLabel.oversizedBehavior,
|
|
363
|
+
forceVisible: this.valueLabel.forceVisible,
|
|
364
|
+
fontSize,
|
|
365
|
+
fontFamily,
|
|
366
|
+
fontWeight,
|
|
367
|
+
};
|
|
368
|
+
const outsideLabels = pieData.map((datum) => {
|
|
369
|
+
const valueLabel = this.resolveValueLabelText(datum.data, total);
|
|
370
|
+
const dimensions = this.measureValueLabelDimensions(valueLabel, labelOverflowOptions);
|
|
371
|
+
return this.resolveOutsideLabel(datum, outerRadius, valueLabel, dimensions.height);
|
|
372
|
+
});
|
|
285
373
|
const adjustedOutsideLabels = this.adjustOutsideLabelPositions(outsideLabels, outerRadius);
|
|
286
374
|
const linesGroup = labelGroup
|
|
287
375
|
.append('g')
|
|
@@ -306,7 +394,7 @@ export class DonutChart extends RadialChartBase {
|
|
|
306
394
|
.attr('fill', 'none')
|
|
307
395
|
.attr('stroke', this.renderTheme.valueLabel.border)
|
|
308
396
|
.attr('stroke-width', 1);
|
|
309
|
-
labelGroup
|
|
397
|
+
const textElement = labelGroup
|
|
310
398
|
.append('text')
|
|
311
399
|
.attr('class', `donut-label donut-label--outside donut-label-${sanitizeForCSS(outsideLabel.datum.data.label)}`)
|
|
312
400
|
.attr('x', textX)
|
|
@@ -316,8 +404,12 @@ export class DonutChart extends RadialChartBase {
|
|
|
316
404
|
.attr('font-family', fontFamily)
|
|
317
405
|
.attr('font-size', `${fontSize}px`)
|
|
318
406
|
.attr('font-weight', fontWeight)
|
|
319
|
-
.attr('fill', this.renderTheme.valueLabel.color)
|
|
320
|
-
|
|
407
|
+
.attr('fill', this.renderTheme.valueLabel.color);
|
|
408
|
+
if (outsideLabel.valueLabel.isCustom) {
|
|
409
|
+
this.renderRadialLabelText(textElement, outsideLabel.valueLabel.fullText, labelOverflowOptions);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
this.renderRadialStructuredLabelText(textElement, outsideLabel.valueLabel.label, outsideLabel.valueLabel.value, outsideLabel.valueLabel.separator, labelOverflowOptions);
|
|
321
413
|
});
|
|
322
414
|
}
|
|
323
415
|
getArcPoint(angle, radius) {
|
|
@@ -326,7 +418,7 @@ export class DonutChart extends RadialChartBase {
|
|
|
326
418
|
y: -Math.cos(angle) * radius,
|
|
327
419
|
};
|
|
328
420
|
}
|
|
329
|
-
resolveOutsideLabel(datum, outerRadius) {
|
|
421
|
+
resolveOutsideLabel(datum, outerRadius, valueLabel, height) {
|
|
330
422
|
const midAngle = (datum.startAngle + datum.endAngle) / 2;
|
|
331
423
|
const point = this.getArcPoint(midAngle, outerRadius + this.valueLabel.outsideOffset);
|
|
332
424
|
const side = point.x >= 0 ? 'right' : 'left';
|
|
@@ -335,8 +427,16 @@ export class DonutChart extends RadialChartBase {
|
|
|
335
427
|
y: point.y,
|
|
336
428
|
side,
|
|
337
429
|
textAnchor: side === 'right' ? 'start' : 'end',
|
|
430
|
+
valueLabel,
|
|
431
|
+
height,
|
|
338
432
|
};
|
|
339
433
|
}
|
|
434
|
+
measureValueLabelDimensions(valueLabel, labelOverflowOptions) {
|
|
435
|
+
if (valueLabel.isCustom) {
|
|
436
|
+
return this.measureRadialLabelDimensions(valueLabel.fullText, labelOverflowOptions);
|
|
437
|
+
}
|
|
438
|
+
return this.measureRadialStructuredLabelDimensions(valueLabel.label, valueLabel.value, valueLabel.separator, labelOverflowOptions);
|
|
439
|
+
}
|
|
340
440
|
adjustOutsideLabelPositions(labels, outerRadius) {
|
|
341
441
|
const adjustForSide = (side) => {
|
|
342
442
|
const sideLabels = labels
|
|
@@ -349,7 +449,8 @@ export class DonutChart extends RadialChartBase {
|
|
|
349
449
|
const bottomLimit = outerRadius;
|
|
350
450
|
sideLabels[0].y = Math.max(topLimit, sideLabels[0].y);
|
|
351
451
|
for (let i = 1; i < sideLabels.length; i++) {
|
|
352
|
-
const minY = sideLabels[i - 1].y +
|
|
452
|
+
const minY = sideLabels[i - 1].y +
|
|
453
|
+
this.getOutsideLabelSpacing(sideLabels[i - 1], sideLabels[i]);
|
|
353
454
|
sideLabels[i].y = Math.max(sideLabels[i].y, minY);
|
|
354
455
|
}
|
|
355
456
|
const overflow = sideLabels[sideLabels.length - 1].y - bottomLimit;
|
|
@@ -357,7 +458,7 @@ export class DonutChart extends RadialChartBase {
|
|
|
357
458
|
sideLabels[sideLabels.length - 1].y -= overflow;
|
|
358
459
|
for (let i = sideLabels.length - 2; i >= 0; i--) {
|
|
359
460
|
const maxY = sideLabels[i + 1].y -
|
|
360
|
-
this.
|
|
461
|
+
this.getOutsideLabelSpacing(sideLabels[i], sideLabels[i + 1]);
|
|
361
462
|
sideLabels[i].y = Math.min(sideLabels[i].y, maxY);
|
|
362
463
|
}
|
|
363
464
|
const underflow = topLimit - sideLabels[0].y;
|
|
@@ -371,4 +472,7 @@ export class DonutChart extends RadialChartBase {
|
|
|
371
472
|
};
|
|
372
473
|
return [...adjustForSide('left'), ...adjustForSide('right')];
|
|
373
474
|
}
|
|
475
|
+
getOutsideLabelSpacing(previousLabel, currentLabel) {
|
|
476
|
+
return Math.max(this.valueLabel.minVerticalSpacing, (previousLabel.height + currentLabel.height) / 2);
|
|
477
|
+
}
|
|
374
478
|
}
|
package/dist/easing.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createCubicBezierEasing(x1: number, y1: number, x2: number, y2: number): (progress: number) => number;
|
package/dist/easing.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const CUBIC_BEZIER_SAMPLE_STEPS = 24;
|
|
2
|
+
export function createCubicBezierEasing(x1, y1, x2, y2) {
|
|
3
|
+
return (progress) => {
|
|
4
|
+
if (progress <= 0) {
|
|
5
|
+
return 0;
|
|
6
|
+
}
|
|
7
|
+
if (progress >= 1) {
|
|
8
|
+
return 1;
|
|
9
|
+
}
|
|
10
|
+
let lower = 0;
|
|
11
|
+
let upper = 1;
|
|
12
|
+
let parameter = progress;
|
|
13
|
+
for (let step = 0; step < CUBIC_BEZIER_SAMPLE_STEPS; step += 1) {
|
|
14
|
+
parameter = (lower + upper) / 2;
|
|
15
|
+
if (sampleCubicBezier(parameter, x1, x2) < progress) {
|
|
16
|
+
lower = parameter;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
upper = parameter;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return sampleCubicBezier(parameter, y1, y2);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function sampleCubicBezier(parameter, controlPoint1, controlPoint2) {
|
|
26
|
+
const inverseParameter = 1 - parameter;
|
|
27
|
+
return (3 * inverseParameter * inverseParameter * parameter * controlPoint1 +
|
|
28
|
+
3 * inverseParameter * parameter * parameter * controlPoint2 +
|
|
29
|
+
parameter * parameter * parameter);
|
|
30
|
+
}
|
package/dist/gauge-chart.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DataItem, LegendSeries } from './types.js';
|
|
1
|
+
import type { DataItem, LabelOversizedBehavior, LegendSeries } from './types.js';
|
|
2
2
|
import type { BaseChart, BaseChartConfig, BaseRenderContext } from './base-chart.js';
|
|
3
3
|
import type { ChartComponentBase } from './chart-interface.js';
|
|
4
4
|
import { RadialChartBase } from './radial-chart-base.js';
|
|
@@ -35,8 +35,11 @@ export type GaugeLabelStyle = {
|
|
|
35
35
|
fontFamily?: string;
|
|
36
36
|
fontWeight?: number | string;
|
|
37
37
|
color?: string;
|
|
38
|
+
maxLabelWidth?: number;
|
|
39
|
+
oversizedBehavior?: LabelOversizedBehavior;
|
|
40
|
+
forceVisible?: boolean;
|
|
38
41
|
};
|
|
39
|
-
export type GaugeAnimationEasingPreset = 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'bounce-out' | 'elastic-out';
|
|
42
|
+
export type GaugeAnimationEasingPreset = 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'bounce-out' | 'elastic-out' | 'spring-out';
|
|
40
43
|
export type GaugeAnimationConfig = {
|
|
41
44
|
show?: boolean;
|
|
42
45
|
duration?: number;
|
|
@@ -116,6 +119,7 @@ export declare class GaugeChart extends RadialChartBase {
|
|
|
116
119
|
private interpolateLinearEasing;
|
|
117
120
|
private normalizeTickLabelStyle;
|
|
118
121
|
private normalizeValueLabelStyle;
|
|
122
|
+
private normalizeGaugeLabelStyle;
|
|
119
123
|
private validateGaugeConfig;
|
|
120
124
|
private validateGaugeRange;
|
|
121
125
|
private validateGaugeGeometry;
|
|
@@ -164,6 +168,7 @@ export declare class GaugeChart extends RadialChartBase {
|
|
|
164
168
|
private renderNeedle;
|
|
165
169
|
private renderCurrentValueMarker;
|
|
166
170
|
private renderValueText;
|
|
171
|
+
private renderGaugeLabelText;
|
|
167
172
|
private attachTooltipLayer;
|
|
168
173
|
private buildTooltipContent;
|
|
169
174
|
protected getLegendSeries(): LegendSeries[];
|
package/dist/gauge-chart.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { arc, easeBounceOut, easeCubicIn, easeCubicInOut, easeCubicOut, easeElasticOut, easeLinear, interpolateNumber, } from 'd3';
|
|
2
|
+
import { createCubicBezierEasing } from './easing.js';
|
|
2
3
|
import { DEFAULT_COLOR_PALETTE } from './theme.js';
|
|
3
4
|
import { ChartValidator } from './validation.js';
|
|
4
5
|
import { RadialChartBase } from './radial-chart-base.js';
|
|
@@ -37,6 +38,7 @@ const DEFAULT_TICK_LABEL_OFFSET = 12;
|
|
|
37
38
|
const DEFAULT_TICK_LABEL_FONT_SIZE = 11;
|
|
38
39
|
const DEFAULT_TICK_LABEL_FONT_WEIGHT = 'normal';
|
|
39
40
|
const DEFAULT_TICK_LABEL_COLOR = '#4b5563';
|
|
41
|
+
const DEFAULT_LABEL_OVERSIZED_BEHAVIOR = 'truncate';
|
|
40
42
|
const DEFAULT_VALUE_LABEL_FONT_SIZE = 28;
|
|
41
43
|
const DEFAULT_VALUE_LABEL_FONT_WEIGHT = '700';
|
|
42
44
|
const DEFAULT_VALUE_LABEL_COLOR = '#111827';
|
|
@@ -49,6 +51,7 @@ const DEFAULT_PROGRESS_RADIUS_INSET = 2;
|
|
|
49
51
|
const MIN_PROGRESS_BAND_THICKNESS = 1;
|
|
50
52
|
const DEFAULT_HALF_CIRCLE_VALUE_TEXT_Y = 32;
|
|
51
53
|
const DEFAULT_FULL_CIRCLE_VALUE_TEXT_MIN_Y = 22;
|
|
54
|
+
const easeSpringOut = createCubicBezierEasing(0.85, 0, 0.15, 1);
|
|
52
55
|
const GAUGE_ANIMATION_EASING_PRESETS = {
|
|
53
56
|
linear: easeLinear,
|
|
54
57
|
'ease-in': easeCubicIn,
|
|
@@ -56,6 +59,7 @@ const GAUGE_ANIMATION_EASING_PRESETS = {
|
|
|
56
59
|
'ease-in-out': easeCubicInOut,
|
|
57
60
|
'bounce-out': easeBounceOut,
|
|
58
61
|
'elastic-out': easeElasticOut,
|
|
62
|
+
'spring-out': easeSpringOut,
|
|
59
63
|
};
|
|
60
64
|
const DUMMY_ARC_DATUM = {
|
|
61
65
|
innerRadius: 0,
|
|
@@ -537,21 +541,31 @@ export class GaugeChart extends RadialChartBase {
|
|
|
537
541
|
return lastPoint.value;
|
|
538
542
|
}
|
|
539
543
|
normalizeTickLabelStyle(config) {
|
|
540
|
-
return {
|
|
541
|
-
fontSize:
|
|
542
|
-
fontFamily:
|
|
543
|
-
fontWeight:
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
color: config?.color ?? DEFAULT_TICK_LABEL_COLOR,
|
|
547
|
-
};
|
|
544
|
+
return this.normalizeGaugeLabelStyle(config, {
|
|
545
|
+
fontSize: DEFAULT_TICK_LABEL_FONT_SIZE,
|
|
546
|
+
fontFamily: this.theme.axis.fontFamily,
|
|
547
|
+
fontWeight: this.theme.axis.fontWeight ?? DEFAULT_TICK_LABEL_FONT_WEIGHT,
|
|
548
|
+
color: DEFAULT_TICK_LABEL_COLOR,
|
|
549
|
+
});
|
|
548
550
|
}
|
|
549
551
|
normalizeValueLabelStyle(config) {
|
|
552
|
+
return this.normalizeGaugeLabelStyle(config, {
|
|
553
|
+
fontSize: DEFAULT_VALUE_LABEL_FONT_SIZE,
|
|
554
|
+
fontFamily: this.theme.axis.fontFamily,
|
|
555
|
+
fontWeight: DEFAULT_VALUE_LABEL_FONT_WEIGHT,
|
|
556
|
+
color: DEFAULT_VALUE_LABEL_COLOR,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
normalizeGaugeLabelStyle(config, defaults) {
|
|
560
|
+
const resolvedConfig = config ?? {};
|
|
550
561
|
return {
|
|
551
|
-
fontSize:
|
|
552
|
-
fontFamily:
|
|
553
|
-
fontWeight:
|
|
554
|
-
color:
|
|
562
|
+
fontSize: resolveDefault(resolvedConfig.fontSize, defaults.fontSize),
|
|
563
|
+
fontFamily: resolveDefault(resolvedConfig.fontFamily, defaults.fontFamily),
|
|
564
|
+
fontWeight: resolveDefault(resolvedConfig.fontWeight, defaults.fontWeight),
|
|
565
|
+
color: resolveDefault(resolvedConfig.color, defaults.color),
|
|
566
|
+
maxLabelWidth: resolvedConfig.maxLabelWidth,
|
|
567
|
+
oversizedBehavior: resolveDefault(resolvedConfig.oversizedBehavior, DEFAULT_LABEL_OVERSIZED_BEHAVIOR),
|
|
568
|
+
forceVisible: resolvedConfig.forceVisible ?? false,
|
|
555
569
|
};
|
|
556
570
|
}
|
|
557
571
|
validateGaugeConfig() {
|
|
@@ -1107,7 +1121,8 @@ export class GaugeChart extends RadialChartBase {
|
|
|
1107
1121
|
: labelPoint.x > 0
|
|
1108
1122
|
? 'start'
|
|
1109
1123
|
: 'end';
|
|
1110
|
-
|
|
1124
|
+
const tickLabelText = this.ticks.formatter(value);
|
|
1125
|
+
const textElement = tickGroup
|
|
1111
1126
|
.append('text')
|
|
1112
1127
|
.attr('class', 'gauge-tick-label')
|
|
1113
1128
|
.attr('x', labelPoint.x)
|
|
@@ -1117,8 +1132,8 @@ export class GaugeChart extends RadialChartBase {
|
|
|
1117
1132
|
.attr('font-size', this.tickLabelStyle.fontSize)
|
|
1118
1133
|
.attr('font-family', this.tickLabelStyle.fontFamily)
|
|
1119
1134
|
.attr('font-weight', this.tickLabelStyle.fontWeight)
|
|
1120
|
-
.attr('fill', this.tickLabelStyle.color)
|
|
1121
|
-
|
|
1135
|
+
.attr('fill', this.tickLabelStyle.color);
|
|
1136
|
+
this.renderGaugeLabelText(textElement, tickLabelText, this.tickLabelStyle);
|
|
1122
1137
|
}
|
|
1123
1138
|
}
|
|
1124
1139
|
renderTargetMarker(gaugeGroup, innerRadius, outerRadius) {
|
|
@@ -1243,8 +1258,8 @@ export class GaugeChart extends RadialChartBase {
|
|
|
1243
1258
|
.attr('font-size', this.valueLabelStyle.fontSize)
|
|
1244
1259
|
.attr('font-weight', this.valueLabelStyle.fontWeight)
|
|
1245
1260
|
.attr('font-family', this.valueLabelStyle.fontFamily)
|
|
1246
|
-
.attr('fill', this.valueLabelStyle.color)
|
|
1247
|
-
|
|
1261
|
+
.attr('fill', this.valueLabelStyle.color);
|
|
1262
|
+
this.renderGaugeLabelText(valueText, this.valueFormatter(initialValue), this.valueLabelStyle, 'baseline');
|
|
1248
1263
|
if (shouldAnimate) {
|
|
1249
1264
|
valueText
|
|
1250
1265
|
.transition()
|
|
@@ -1253,11 +1268,21 @@ export class GaugeChart extends RadialChartBase {
|
|
|
1253
1268
|
.tween('text', () => {
|
|
1254
1269
|
return (progress) => {
|
|
1255
1270
|
const currentValue = startValue + (this.value - startValue) * progress;
|
|
1256
|
-
|
|
1271
|
+
this.renderGaugeLabelText(valueText, this.valueFormatter(currentValue), this.valueLabelStyle, 'baseline');
|
|
1257
1272
|
};
|
|
1258
1273
|
});
|
|
1259
1274
|
}
|
|
1260
1275
|
}
|
|
1276
|
+
renderGaugeLabelText(textElement, text, labelStyle, verticalAnchor = 'middle') {
|
|
1277
|
+
this.renderRadialLabelText(textElement, text, {
|
|
1278
|
+
maxLabelWidth: labelStyle.maxLabelWidth,
|
|
1279
|
+
oversizedBehavior: labelStyle.oversizedBehavior,
|
|
1280
|
+
forceVisible: labelStyle.forceVisible,
|
|
1281
|
+
fontSize: labelStyle.fontSize,
|
|
1282
|
+
fontFamily: labelStyle.fontFamily,
|
|
1283
|
+
fontWeight: labelStyle.fontWeight,
|
|
1284
|
+
}, verticalAnchor);
|
|
1285
|
+
}
|
|
1261
1286
|
attachTooltipLayer(gaugeGroup, innerRadius, outerRadius, progressColor) {
|
|
1262
1287
|
const interactionArc = arc()
|
|
1263
1288
|
.innerRadius(Math.max(0, innerRadius - 20))
|
package/dist/line.js
CHANGED
|
@@ -267,6 +267,7 @@ export class Line {
|
|
|
267
267
|
const border = config.border ?? theme.valueLabel.border;
|
|
268
268
|
const borderRadius = config.borderRadius ?? theme.valueLabel.borderRadius;
|
|
269
269
|
const padding = config.padding ?? theme.valueLabel.padding;
|
|
270
|
+
const forceVisible = config.forceVisible === true;
|
|
270
271
|
const labelGroup = plotGroup
|
|
271
272
|
.append('g')
|
|
272
273
|
.attr('class', `line-value-labels-${sanitizeForCSS(this.dataKey)}`);
|
|
@@ -299,7 +300,7 @@ export class Line {
|
|
|
299
300
|
labelY = yPos + boxHeight / 2 + theme.line.point.size + 4;
|
|
300
301
|
// Check if it fits below
|
|
301
302
|
if (labelY + boxHeight / 2 > plotBottom - 4) {
|
|
302
|
-
shouldRender =
|
|
303
|
+
shouldRender = forceVisible;
|
|
303
304
|
}
|
|
304
305
|
}
|
|
305
306
|
tempText.remove();
|
package/dist/pie-chart.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import type { DataItem, LegendSeries } from './types.js';
|
|
1
|
+
import type { DataItem, LabelOversizedBehavior, LegendSeries } from './types.js';
|
|
2
2
|
import { type BaseChartConfig, type BaseRenderContext } from './base-chart.js';
|
|
3
3
|
import type { ChartComponentBase } from './chart-interface.js';
|
|
4
4
|
import { RadialChartBase } from './radial-chart-base.js';
|
|
5
|
+
import { type RadialAnimationConfig, type RadialAnimationEasingPreset } from './radial-animation.js';
|
|
6
|
+
export type PieAnimationConfig = RadialAnimationConfig;
|
|
7
|
+
export type PieAnimationEasingPreset = RadialAnimationEasingPreset;
|
|
5
8
|
export type PieSort = 'none' | 'ascending' | 'descending' | ((a: PieSegmentData, b: PieSegmentData) => number);
|
|
6
9
|
export type PieConfig = {
|
|
7
10
|
innerRadius?: number;
|
|
@@ -12,6 +15,7 @@ export type PieConfig = {
|
|
|
12
15
|
sort?: PieSort;
|
|
13
16
|
};
|
|
14
17
|
export type PieValueLabelPosition = 'inside' | 'outside' | 'auto';
|
|
18
|
+
export type PieValueLabelFormatter = (label: string, value: number, data: DataItem, percentage: number) => string;
|
|
15
19
|
export type PieValueLabelConfig = {
|
|
16
20
|
show?: boolean;
|
|
17
21
|
position?: PieValueLabelPosition;
|
|
@@ -19,11 +23,18 @@ export type PieValueLabelConfig = {
|
|
|
19
23
|
outsideOffset?: number;
|
|
20
24
|
insideMargin?: number;
|
|
21
25
|
minVerticalSpacing?: number;
|
|
22
|
-
|
|
26
|
+
maxLabelWidth?: number;
|
|
27
|
+
oversizedBehavior?: LabelOversizedBehavior;
|
|
28
|
+
forceVisible?: boolean;
|
|
29
|
+
labelFormatter?: PieValueLabelFormatter;
|
|
30
|
+
valueFormatter?: PieValueLabelFormatter;
|
|
31
|
+
separator?: string;
|
|
32
|
+
formatter?: PieValueLabelFormatter;
|
|
23
33
|
};
|
|
24
34
|
export type PieChartConfig = BaseChartConfig & {
|
|
25
35
|
pie?: PieConfig;
|
|
26
36
|
valueLabel?: PieValueLabelConfig;
|
|
37
|
+
animate?: boolean | PieAnimationConfig;
|
|
27
38
|
valueKey?: string;
|
|
28
39
|
labelKey?: string;
|
|
29
40
|
};
|
|
@@ -43,6 +54,7 @@ export declare class PieChart extends RadialChartBase {
|
|
|
43
54
|
private readonly valueKey;
|
|
44
55
|
private readonly labelKey;
|
|
45
56
|
private readonly valueLabel;
|
|
57
|
+
private readonly motionController;
|
|
46
58
|
private segments;
|
|
47
59
|
constructor(config: PieChartConfig);
|
|
48
60
|
private validatePieData;
|
|
@@ -53,7 +65,9 @@ export declare class PieChart extends RadialChartBase {
|
|
|
53
65
|
private warnOnTinySlices;
|
|
54
66
|
protected getExportComponents(): ChartComponentBase[];
|
|
55
67
|
update(data: DataItem[]): void;
|
|
56
|
-
|
|
68
|
+
protected prepareForLegendChange(): void;
|
|
69
|
+
private resolveValueLabelText;
|
|
70
|
+
private getValueLabelPercentage;
|
|
57
71
|
protected createExportChart(): RadialChartBase;
|
|
58
72
|
protected syncDerivedState(): void;
|
|
59
73
|
protected renderChart({ svg, plotGroup, plotArea, }: BaseRenderContext): void;
|
|
@@ -69,6 +83,8 @@ export declare class PieChart extends RadialChartBase {
|
|
|
69
83
|
private resolveValueLabelPlacement;
|
|
70
84
|
private canFitInsideLabel;
|
|
71
85
|
private resolveOutsideLabel;
|
|
86
|
+
private measureValueLabelDimensions;
|
|
72
87
|
private adjustOutsideLabelPositions;
|
|
88
|
+
private getOutsideLabelSpacing;
|
|
73
89
|
}
|
|
74
90
|
export {};
|