@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
package/dist/gauge-chart.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { arc, easeBounceOut, easeCubicIn, easeCubicInOut, easeCubicOut, easeElasticOut, easeLinear, interpolateNumber,
|
|
2
|
-
import { BaseChart, } from './base-chart.js';
|
|
1
|
+
import { arc, easeBounceOut, easeCubicIn, easeCubicInOut, easeCubicOut, easeElasticOut, easeLinear, interpolateNumber, } from 'd3';
|
|
3
2
|
import { DEFAULT_COLOR_PALETTE } from './theme.js';
|
|
4
3
|
import { ChartValidator } from './validation.js';
|
|
4
|
+
import { RadialChartBase } from './radial-chart-base.js';
|
|
5
|
+
function resolveDefault(value, fallback) {
|
|
6
|
+
return value === undefined ? fallback : value;
|
|
7
|
+
}
|
|
5
8
|
const DEFAULT_START_ANGLE = -Math.PI * 0.75;
|
|
6
9
|
const DEFAULT_END_ANGLE = Math.PI * 0.75;
|
|
7
10
|
const DEFAULT_HALF_START_ANGLE = -Math.PI / 2;
|
|
@@ -46,8 +49,6 @@ const DEFAULT_PROGRESS_RADIUS_INSET = 2;
|
|
|
46
49
|
const MIN_PROGRESS_BAND_THICKNESS = 1;
|
|
47
50
|
const DEFAULT_HALF_CIRCLE_VALUE_TEXT_Y = 32;
|
|
48
51
|
const DEFAULT_FULL_CIRCLE_VALUE_TEXT_MIN_Y = 22;
|
|
49
|
-
const TOOLTIP_OFFSET_PX = 12;
|
|
50
|
-
const EDGE_MARGIN_PX = 10;
|
|
51
52
|
const GAUGE_ANIMATION_EASING_PRESETS = {
|
|
52
53
|
linear: easeLinear,
|
|
53
54
|
'ease-in': easeCubicIn,
|
|
@@ -62,7 +63,7 @@ const DUMMY_ARC_DATUM = {
|
|
|
62
63
|
startAngle: 0,
|
|
63
64
|
endAngle: 0,
|
|
64
65
|
};
|
|
65
|
-
export class GaugeChart extends
|
|
66
|
+
export class GaugeChart extends RadialChartBase {
|
|
66
67
|
constructor(config) {
|
|
67
68
|
super(config);
|
|
68
69
|
Object.defineProperty(this, "configuredValue", {
|
|
@@ -251,32 +252,27 @@ export class GaugeChart extends BaseChart {
|
|
|
251
252
|
}
|
|
252
253
|
});
|
|
253
254
|
const gauge = config.gauge ?? {};
|
|
254
|
-
this.
|
|
255
|
-
this.
|
|
256
|
-
this.
|
|
257
|
-
this.
|
|
258
|
-
this.
|
|
259
|
-
this.
|
|
260
|
-
this.
|
|
255
|
+
const resolvedGauge = this.resolveGaugeConstructorValues(config, gauge);
|
|
256
|
+
this.configuredValue = resolvedGauge.configuredValue;
|
|
257
|
+
this.configuredTargetValue = resolvedGauge.configuredTargetValue;
|
|
258
|
+
this.configuredSegments = resolvedGauge.configuredSegments;
|
|
259
|
+
this.valueKey = resolvedGauge.valueKey;
|
|
260
|
+
this.targetValueKey = resolvedGauge.targetValueKey;
|
|
261
|
+
this.minValue = resolvedGauge.minValue;
|
|
262
|
+
this.maxValue = resolvedGauge.maxValue;
|
|
261
263
|
this.animation = this.normalizeAnimationConfig(gauge.animate);
|
|
262
|
-
this.halfCircle =
|
|
263
|
-
this.startAngle =
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
this.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this.
|
|
270
|
-
this.
|
|
271
|
-
this.
|
|
272
|
-
this.
|
|
273
|
-
this.
|
|
274
|
-
gauge.progressColor ??
|
|
275
|
-
this.getThemePaletteColor(DEFAULT_THEME_PALETTE_INDEX);
|
|
276
|
-
this.targetColor = gauge.targetColor ?? DEFAULT_TARGET_COLOR;
|
|
277
|
-
this.segmentStyle = gauge.segmentStyle ?? DEFAULT_SEGMENT_STYLE;
|
|
278
|
-
this.valueFormatter = gauge.valueFormatter ?? this.defaultFormat;
|
|
279
|
-
this.showValue = gauge.showValue ?? DEFAULT_SHOW_VALUE;
|
|
264
|
+
this.halfCircle = resolvedGauge.halfCircle;
|
|
265
|
+
this.startAngle = resolvedGauge.startAngle;
|
|
266
|
+
this.endAngle = resolvedGauge.endAngle;
|
|
267
|
+
this.innerRadiusRatio = resolvedGauge.innerRadiusRatio;
|
|
268
|
+
this.thickness = resolvedGauge.thickness;
|
|
269
|
+
this.cornerRadius = resolvedGauge.cornerRadius;
|
|
270
|
+
this.trackColor = resolvedGauge.trackColor;
|
|
271
|
+
this.progressColor = resolvedGauge.progressColor;
|
|
272
|
+
this.targetColor = resolvedGauge.targetColor;
|
|
273
|
+
this.segmentStyle = resolvedGauge.segmentStyle;
|
|
274
|
+
this.valueFormatter = resolvedGauge.valueFormatter;
|
|
275
|
+
this.showValue = resolvedGauge.showValue;
|
|
280
276
|
this.needle = this.normalizeNeedleConfig(gauge.needle);
|
|
281
277
|
this.marker = this.normalizeMarkerConfig(gauge.marker, !this.needle.show);
|
|
282
278
|
this.ticks = this.normalizeTickConfig(gauge.ticks);
|
|
@@ -286,6 +282,30 @@ export class GaugeChart extends BaseChart {
|
|
|
286
282
|
this.segments = this.prepareSegments();
|
|
287
283
|
this.initializeDataState();
|
|
288
284
|
}
|
|
285
|
+
resolveGaugeConstructorValues(config, gauge) {
|
|
286
|
+
const halfCircle = resolveDefault(gauge.halfCircle, DEFAULT_HALF_CIRCLE);
|
|
287
|
+
return {
|
|
288
|
+
configuredValue: gauge.value,
|
|
289
|
+
configuredTargetValue: gauge.targetValue,
|
|
290
|
+
configuredSegments: resolveDefault(gauge.segments, []),
|
|
291
|
+
valueKey: resolveDefault(config.valueKey, DEFAULT_VALUE_KEY),
|
|
292
|
+
targetValueKey: config.targetValueKey,
|
|
293
|
+
minValue: resolveDefault(gauge.min, DEFAULT_MIN_VALUE),
|
|
294
|
+
maxValue: resolveDefault(gauge.max, DEFAULT_MAX_VALUE),
|
|
295
|
+
halfCircle,
|
|
296
|
+
startAngle: resolveDefault(gauge.startAngle, halfCircle ? DEFAULT_HALF_START_ANGLE : DEFAULT_START_ANGLE),
|
|
297
|
+
endAngle: resolveDefault(gauge.endAngle, halfCircle ? DEFAULT_HALF_END_ANGLE : DEFAULT_END_ANGLE),
|
|
298
|
+
innerRadiusRatio: resolveDefault(gauge.innerRadius, DEFAULT_INNER_RADIUS_RATIO),
|
|
299
|
+
thickness: resolveDefault(gauge.thickness, null),
|
|
300
|
+
cornerRadius: resolveDefault(gauge.cornerRadius, DEFAULT_CORNER_RADIUS),
|
|
301
|
+
trackColor: resolveDefault(gauge.trackColor, DEFAULT_TRACK_COLOR),
|
|
302
|
+
progressColor: resolveDefault(gauge.progressColor, this.getThemePaletteColor(DEFAULT_THEME_PALETTE_INDEX)),
|
|
303
|
+
targetColor: resolveDefault(gauge.targetColor, DEFAULT_TARGET_COLOR),
|
|
304
|
+
segmentStyle: resolveDefault(gauge.segmentStyle, DEFAULT_SEGMENT_STYLE),
|
|
305
|
+
valueFormatter: resolveDefault(gauge.valueFormatter, this.defaultFormat),
|
|
306
|
+
showValue: resolveDefault(gauge.showValue, DEFAULT_SHOW_VALUE),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
289
309
|
normalizeNeedleConfig(config) {
|
|
290
310
|
if (config === false) {
|
|
291
311
|
return {
|
|
@@ -342,14 +362,24 @@ export class GaugeChart extends BaseChart {
|
|
|
342
362
|
return palette[index % palette.length];
|
|
343
363
|
}
|
|
344
364
|
normalizeTickConfig(config) {
|
|
365
|
+
const resolvedConfig = {
|
|
366
|
+
count: DEFAULT_TICK_COUNT,
|
|
367
|
+
show: DEFAULT_TICKS_SHOW,
|
|
368
|
+
showLines: DEFAULT_TICKS_SHOW_LINES,
|
|
369
|
+
showLabels: DEFAULT_TICKS_SHOW_LABELS,
|
|
370
|
+
size: DEFAULT_TICK_SIZE,
|
|
371
|
+
labelOffset: DEFAULT_TICK_LABEL_OFFSET,
|
|
372
|
+
formatter: this.defaultFormat,
|
|
373
|
+
...config,
|
|
374
|
+
};
|
|
345
375
|
return {
|
|
346
|
-
count:
|
|
347
|
-
show:
|
|
348
|
-
showLines:
|
|
349
|
-
showLabels:
|
|
350
|
-
size:
|
|
351
|
-
labelOffset:
|
|
352
|
-
formatter:
|
|
376
|
+
count: resolvedConfig.count,
|
|
377
|
+
show: resolvedConfig.show,
|
|
378
|
+
showLines: resolvedConfig.showLines,
|
|
379
|
+
showLabels: resolvedConfig.showLabels,
|
|
380
|
+
size: resolvedConfig.size,
|
|
381
|
+
labelOffset: resolvedConfig.labelOffset,
|
|
382
|
+
formatter: resolvedConfig.formatter,
|
|
353
383
|
};
|
|
354
384
|
}
|
|
355
385
|
normalizeAnimationConfig(config) {
|
|
@@ -405,35 +435,51 @@ export class GaugeChart extends BaseChart {
|
|
|
405
435
|
if (tokens.length < 2) {
|
|
406
436
|
return null;
|
|
407
437
|
}
|
|
408
|
-
const rawStops = tokens.map((token) =>
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
return { value, position: undefined };
|
|
419
|
-
}
|
|
420
|
-
const percentageToken = parts[1];
|
|
421
|
-
if (!percentageToken.endsWith('%')) {
|
|
422
|
-
return null;
|
|
423
|
-
}
|
|
424
|
-
const percentageValue = Number(percentageToken.slice(0, -1));
|
|
425
|
-
if (!Number.isFinite(percentageValue)) {
|
|
426
|
-
return null;
|
|
427
|
-
}
|
|
438
|
+
const rawStops = tokens.map((token) => this.parseLinearEasingStop(token));
|
|
439
|
+
if (rawStops.some((stop) => stop === null)) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
const stops = rawStops;
|
|
443
|
+
const positions = this.resolveLinearEasingPositions(stops);
|
|
444
|
+
if (!positions) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
const easingPoints = stops.map((stop, index) => {
|
|
428
448
|
return {
|
|
429
|
-
value,
|
|
430
|
-
position:
|
|
449
|
+
value: stop.value,
|
|
450
|
+
position: positions[index],
|
|
431
451
|
};
|
|
432
452
|
});
|
|
433
|
-
|
|
453
|
+
return (progress) => {
|
|
454
|
+
return this.interpolateLinearEasing(progress, easingPoints);
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
parseLinearEasingStop(token) {
|
|
458
|
+
const parts = token.split(/\s+/).filter(Boolean);
|
|
459
|
+
if (parts.length === 0 || parts.length > 2) {
|
|
434
460
|
return null;
|
|
435
461
|
}
|
|
436
|
-
const
|
|
462
|
+
const value = Number(parts[0]);
|
|
463
|
+
if (!Number.isFinite(value)) {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
if (parts.length === 1) {
|
|
467
|
+
return { value, position: undefined };
|
|
468
|
+
}
|
|
469
|
+
const percentageToken = parts[1];
|
|
470
|
+
if (!percentageToken.endsWith('%')) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
const percentageValue = Number(percentageToken.slice(0, -1));
|
|
474
|
+
if (!Number.isFinite(percentageValue)) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
value,
|
|
479
|
+
position: percentageValue / 100,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
resolveLinearEasingPositions(stops) {
|
|
437
483
|
const positions = stops.map((stop) => stop.position);
|
|
438
484
|
if (positions[0] === undefined) {
|
|
439
485
|
positions[0] = 0;
|
|
@@ -451,47 +497,44 @@ export class GaugeChart extends BaseChart {
|
|
|
451
497
|
if (currentPosition < previousPosition) {
|
|
452
498
|
return null;
|
|
453
499
|
}
|
|
454
|
-
|
|
455
|
-
if (missingCount > 0) {
|
|
456
|
-
for (let missingIndex = 1; missingIndex <= missingCount; missingIndex += 1) {
|
|
457
|
-
const ratio = missingIndex / (missingCount + 1);
|
|
458
|
-
positions[previousDefinedIndex + missingIndex] =
|
|
459
|
-
previousPosition +
|
|
460
|
-
(currentPosition - previousPosition) * ratio;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
500
|
+
this.fillMissingLinearEasingPositions(positions, previousDefinedIndex, currentIndex);
|
|
463
501
|
previousDefinedIndex = currentIndex;
|
|
464
502
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
503
|
+
return positions;
|
|
504
|
+
}
|
|
505
|
+
fillMissingLinearEasingPositions(positions, previousDefinedIndex, currentIndex) {
|
|
506
|
+
const previousPosition = positions[previousDefinedIndex];
|
|
507
|
+
const currentPosition = positions[currentIndex];
|
|
508
|
+
const missingCount = currentIndex - previousDefinedIndex - 1;
|
|
509
|
+
for (let missingIndex = 1; missingIndex <= missingCount; missingIndex += 1) {
|
|
510
|
+
const ratio = missingIndex / (missingCount + 1);
|
|
511
|
+
positions[previousDefinedIndex + missingIndex] =
|
|
512
|
+
previousPosition + (currentPosition - previousPosition) * ratio;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
interpolateLinearEasing(progress, easingPoints) {
|
|
516
|
+
const clamped = Math.max(0, Math.min(1, progress));
|
|
517
|
+
if (clamped <= easingPoints[0].position) {
|
|
518
|
+
return easingPoints[0].value;
|
|
519
|
+
}
|
|
520
|
+
const lastPoint = easingPoints[easingPoints.length - 1];
|
|
521
|
+
if (clamped >= lastPoint.position) {
|
|
522
|
+
return lastPoint.value;
|
|
523
|
+
}
|
|
524
|
+
for (let index = 0; index < easingPoints.length - 1; index += 1) {
|
|
525
|
+
const left = easingPoints[index];
|
|
526
|
+
const right = easingPoints[index + 1];
|
|
527
|
+
if (clamped > right.position) {
|
|
528
|
+
continue;
|
|
479
529
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
if (clamped > right.position) {
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
const span = right.position - left.position;
|
|
487
|
-
if (span <= 0) {
|
|
488
|
-
return right.value;
|
|
489
|
-
}
|
|
490
|
-
const localProgress = (clamped - left.position) / span;
|
|
491
|
-
return left.value + (right.value - left.value) * localProgress;
|
|
530
|
+
const span = right.position - left.position;
|
|
531
|
+
if (span <= 0) {
|
|
532
|
+
return right.value;
|
|
492
533
|
}
|
|
493
|
-
|
|
494
|
-
|
|
534
|
+
const localProgress = (clamped - left.position) / span;
|
|
535
|
+
return left.value + (right.value - left.value) * localProgress;
|
|
536
|
+
}
|
|
537
|
+
return lastPoint.value;
|
|
495
538
|
}
|
|
496
539
|
normalizeTickLabelStyle(config) {
|
|
497
540
|
return {
|
|
@@ -512,6 +555,14 @@ export class GaugeChart extends BaseChart {
|
|
|
512
555
|
};
|
|
513
556
|
}
|
|
514
557
|
validateGaugeConfig() {
|
|
558
|
+
this.validateGaugeRange();
|
|
559
|
+
this.validateGaugeGeometry();
|
|
560
|
+
this.validateTickSettings();
|
|
561
|
+
this.validateIndicatorSettings();
|
|
562
|
+
this.validateAnimationSettings();
|
|
563
|
+
this.validateSegments(this.configuredSegments);
|
|
564
|
+
}
|
|
565
|
+
validateGaugeRange() {
|
|
515
566
|
if (!Number.isFinite(this.minValue) ||
|
|
516
567
|
!Number.isFinite(this.maxValue)) {
|
|
517
568
|
throw new Error(`GaugeChart: gauge.min and gauge.max must be finite numbers, received min='${this.minValue}' and max='${this.maxValue}'`);
|
|
@@ -519,6 +570,8 @@ export class GaugeChart extends BaseChart {
|
|
|
519
570
|
if (this.minValue >= this.maxValue) {
|
|
520
571
|
throw new Error(`GaugeChart: gauge.min must be less than gauge.max, received min='${this.minValue}' and max='${this.maxValue}'`);
|
|
521
572
|
}
|
|
573
|
+
}
|
|
574
|
+
validateGaugeGeometry() {
|
|
522
575
|
if (!Number.isFinite(this.startAngle) ||
|
|
523
576
|
!Number.isFinite(this.endAngle)) {
|
|
524
577
|
throw new Error(`GaugeChart: gauge.startAngle and gauge.endAngle must be finite numbers, received startAngle='${this.startAngle}' and endAngle='${this.endAngle}'`);
|
|
@@ -536,6 +589,8 @@ export class GaugeChart extends BaseChart {
|
|
|
536
589
|
(!Number.isFinite(this.thickness) || this.thickness <= 0)) {
|
|
537
590
|
throw new Error(`GaugeChart: gauge.thickness must be > 0 when provided, received '${this.thickness}'`);
|
|
538
591
|
}
|
|
592
|
+
}
|
|
593
|
+
validateTickSettings() {
|
|
539
594
|
if (!Number.isInteger(this.ticks.count) || this.ticks.count < 0) {
|
|
540
595
|
throw new Error(`GaugeChart: gauge.ticks.count must be a non-negative integer, received '${this.ticks.count}'`);
|
|
541
596
|
}
|
|
@@ -545,6 +600,8 @@ export class GaugeChart extends BaseChart {
|
|
|
545
600
|
if (this.ticks.labelOffset < 0) {
|
|
546
601
|
throw new Error(`GaugeChart: gauge.ticks.labelOffset must be >= 0, received '${this.ticks.labelOffset}'`);
|
|
547
602
|
}
|
|
603
|
+
}
|
|
604
|
+
validateIndicatorSettings() {
|
|
548
605
|
if (this.needle.width <= 0) {
|
|
549
606
|
throw new Error(`GaugeChart: gauge.needle.width must be > 0, received '${this.needle.width}'`);
|
|
550
607
|
}
|
|
@@ -557,11 +614,12 @@ export class GaugeChart extends BaseChart {
|
|
|
557
614
|
if (this.marker.width <= 0) {
|
|
558
615
|
throw new Error(`GaugeChart: gauge.marker.width must be > 0, received '${this.marker.width}'`);
|
|
559
616
|
}
|
|
617
|
+
}
|
|
618
|
+
validateAnimationSettings() {
|
|
560
619
|
if (!Number.isFinite(this.animation.duration) ||
|
|
561
620
|
this.animation.duration < 0) {
|
|
562
621
|
throw new Error(`GaugeChart: gauge.animate.duration must be >= 0, received '${this.animation.duration}'`);
|
|
563
622
|
}
|
|
564
|
-
this.validateSegments(this.configuredSegments);
|
|
565
623
|
}
|
|
566
624
|
validateSegments(segments) {
|
|
567
625
|
if (segments.length === 0) {
|
|
@@ -726,34 +784,8 @@ export class GaugeChart extends BaseChart {
|
|
|
726
784
|
renderChart({ svg, plotGroup, plotArea, }) {
|
|
727
785
|
svg.attr('role', 'img').attr('aria-label', this.buildAriaLabel());
|
|
728
786
|
this.renderTitle(svg);
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
}
|
|
732
|
-
const labelAllowance = this.ticks.show && this.ticks.showLabels
|
|
733
|
-
? this.ticks.size + this.ticks.labelOffset + 14
|
|
734
|
-
: this.ticks.show && this.ticks.showLines
|
|
735
|
-
? this.ticks.size + 10
|
|
736
|
-
: 8;
|
|
737
|
-
let outerRadius;
|
|
738
|
-
const centerX = plotArea.left + plotArea.width / 2;
|
|
739
|
-
let centerY;
|
|
740
|
-
if (this.halfCircle) {
|
|
741
|
-
const valueSpace = this.showValue
|
|
742
|
-
? DEFAULT_HALF_CIRCLE_VALUE_SPACE_WITH_LABEL
|
|
743
|
-
: DEFAULT_HALF_CIRCLE_VALUE_SPACE_WITHOUT_LABEL;
|
|
744
|
-
const maxHorizontalRadius = plotArea.width / 2 - labelAllowance - 8;
|
|
745
|
-
const maxVerticalRadius = plotArea.height - valueSpace - labelAllowance - 8;
|
|
746
|
-
outerRadius = Math.max(24, Math.min(maxHorizontalRadius, maxVerticalRadius));
|
|
747
|
-
centerY = plotArea.top + labelAllowance + outerRadius + 4;
|
|
748
|
-
}
|
|
749
|
-
else {
|
|
750
|
-
const maxRadius = Math.min(plotArea.width, plotArea.height) / 2;
|
|
751
|
-
outerRadius = Math.max(24, maxRadius - labelAllowance);
|
|
752
|
-
centerY = plotArea.top + plotArea.height / 2;
|
|
753
|
-
}
|
|
754
|
-
const innerRadius = this.thickness !== null
|
|
755
|
-
? Math.max(0, outerRadius - Math.min(this.thickness, outerRadius - 1))
|
|
756
|
-
: Math.max(8, Math.min(outerRadius - 4, outerRadius * this.innerRadiusRatio));
|
|
787
|
+
this.initializeTooltip();
|
|
788
|
+
const { centerX, centerY, innerRadius, outerRadius } = this.resolveGaugeGeometry(plotArea);
|
|
757
789
|
const gaugeGroup = plotGroup
|
|
758
790
|
.append('g')
|
|
759
791
|
.attr('class', 'gauge')
|
|
@@ -761,20 +793,76 @@ export class GaugeChart extends BaseChart {
|
|
|
761
793
|
const animationStartValue = this.resolveAnimationStartValue();
|
|
762
794
|
this.renderTrack(gaugeGroup, innerRadius, outerRadius);
|
|
763
795
|
const visibleSegments = this.getVisibleSegments();
|
|
796
|
+
const progressColor = this.resolveProgressColor(visibleSegments);
|
|
797
|
+
this.renderSegmentsOrProgress(gaugeGroup, visibleSegments, innerRadius, outerRadius, progressColor, animationStartValue);
|
|
798
|
+
this.renderGaugeLabels(gaugeGroup, outerRadius, animationStartValue);
|
|
799
|
+
this.renderGaugeIndicators(gaugeGroup, innerRadius, outerRadius, animationStartValue);
|
|
800
|
+
this.attachTooltipIfEnabled(gaugeGroup, innerRadius, outerRadius, progressColor);
|
|
801
|
+
this.renderInlineLegend(svg);
|
|
802
|
+
}
|
|
803
|
+
resolveGaugeGeometry(plotArea) {
|
|
804
|
+
const labelAllowance = this.resolveLabelAllowance();
|
|
805
|
+
const centerX = plotArea.left + plotArea.width / 2;
|
|
806
|
+
const { centerY, outerRadius } = this.halfCircle
|
|
807
|
+
? this.resolveHalfCircleGeometry(plotArea, labelAllowance)
|
|
808
|
+
: this.resolveFullCircleGeometry(plotArea, labelAllowance);
|
|
809
|
+
return {
|
|
810
|
+
centerX,
|
|
811
|
+
centerY,
|
|
812
|
+
outerRadius,
|
|
813
|
+
innerRadius: this.resolveInnerRadius(outerRadius),
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
resolveLabelAllowance() {
|
|
817
|
+
if (this.ticks.show && this.ticks.showLabels) {
|
|
818
|
+
return this.ticks.size + this.ticks.labelOffset + 14;
|
|
819
|
+
}
|
|
820
|
+
if (this.ticks.show && this.ticks.showLines) {
|
|
821
|
+
return this.ticks.size + 10;
|
|
822
|
+
}
|
|
823
|
+
return 8;
|
|
824
|
+
}
|
|
825
|
+
resolveHalfCircleGeometry(plotArea, labelAllowance) {
|
|
826
|
+
const valueSpace = this.showValue
|
|
827
|
+
? DEFAULT_HALF_CIRCLE_VALUE_SPACE_WITH_LABEL
|
|
828
|
+
: DEFAULT_HALF_CIRCLE_VALUE_SPACE_WITHOUT_LABEL;
|
|
829
|
+
const maxHorizontalRadius = plotArea.width / 2 - labelAllowance - 8;
|
|
830
|
+
const maxVerticalRadius = plotArea.height - valueSpace - labelAllowance - 8;
|
|
831
|
+
const outerRadius = Math.max(24, Math.min(maxHorizontalRadius, maxVerticalRadius));
|
|
832
|
+
return {
|
|
833
|
+
outerRadius,
|
|
834
|
+
centerY: plotArea.top + labelAllowance + outerRadius + 4,
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
resolveFullCircleGeometry(plotArea, labelAllowance) {
|
|
838
|
+
const maxRadius = Math.min(plotArea.width, plotArea.height) / 2;
|
|
839
|
+
return {
|
|
840
|
+
outerRadius: Math.max(24, maxRadius - labelAllowance),
|
|
841
|
+
centerY: plotArea.top + plotArea.height / 2,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
resolveInnerRadius(outerRadius) {
|
|
845
|
+
if (this.thickness !== null) {
|
|
846
|
+
return Math.max(0, outerRadius - Math.min(this.thickness, outerRadius - 1));
|
|
847
|
+
}
|
|
848
|
+
return Math.max(8, Math.min(outerRadius - 4, outerRadius * this.innerRadiusRatio));
|
|
849
|
+
}
|
|
850
|
+
renderSegmentsOrProgress(gaugeGroup, visibleSegments, innerRadius, outerRadius, progressColor, animationStartValue) {
|
|
764
851
|
if (visibleSegments.length > 0) {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
}
|
|
768
|
-
else {
|
|
769
|
-
this.renderSegments(gaugeGroup, visibleSegments, innerRadius, outerRadius);
|
|
770
|
-
}
|
|
852
|
+
this.renderVisibleSegments(gaugeGroup, visibleSegments, innerRadius, outerRadius);
|
|
853
|
+
return;
|
|
771
854
|
}
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
855
|
+
const progressRadii = this.getProgressRadii(innerRadius, outerRadius);
|
|
856
|
+
this.renderProgress(gaugeGroup, progressColor, progressRadii.inner, progressRadii.outer, animationStartValue);
|
|
857
|
+
}
|
|
858
|
+
renderVisibleSegments(gaugeGroup, visibleSegments, innerRadius, outerRadius) {
|
|
859
|
+
if (this.segmentStyle === 'gradient') {
|
|
860
|
+
this.renderGradientSegments(gaugeGroup, visibleSegments, innerRadius, outerRadius);
|
|
861
|
+
return;
|
|
777
862
|
}
|
|
863
|
+
this.renderSegments(gaugeGroup, visibleSegments, innerRadius, outerRadius);
|
|
864
|
+
}
|
|
865
|
+
renderGaugeLabels(gaugeGroup, outerRadius, animationStartValue) {
|
|
778
866
|
if (this.ticks.show &&
|
|
779
867
|
this.ticks.count > 0 &&
|
|
780
868
|
(this.ticks.showLines || this.ticks.showLabels)) {
|
|
@@ -783,19 +871,23 @@ export class GaugeChart extends BaseChart {
|
|
|
783
871
|
if (this.showValue) {
|
|
784
872
|
this.renderValueText(gaugeGroup, outerRadius, animationStartValue);
|
|
785
873
|
}
|
|
874
|
+
}
|
|
875
|
+
renderGaugeIndicators(gaugeGroup, innerRadius, outerRadius, animationStartValue) {
|
|
786
876
|
if (this.targetValue !== null) {
|
|
787
877
|
this.renderTargetMarker(gaugeGroup, innerRadius, outerRadius);
|
|
788
878
|
}
|
|
789
879
|
if (this.needle.show) {
|
|
790
880
|
this.renderNeedle(gaugeGroup, innerRadius, outerRadius, animationStartValue);
|
|
881
|
+
return;
|
|
791
882
|
}
|
|
792
|
-
|
|
883
|
+
if (this.marker.show) {
|
|
793
884
|
this.renderCurrentValueMarker(gaugeGroup, innerRadius, outerRadius, animationStartValue);
|
|
794
885
|
}
|
|
886
|
+
}
|
|
887
|
+
attachTooltipIfEnabled(gaugeGroup, innerRadius, outerRadius, progressColor) {
|
|
795
888
|
if (this.tooltip) {
|
|
796
889
|
this.attachTooltipLayer(gaugeGroup, innerRadius, outerRadius, progressColor);
|
|
797
890
|
}
|
|
798
|
-
this.renderInlineLegend(svg);
|
|
799
891
|
}
|
|
800
892
|
resolveAnimationStartValue() {
|
|
801
893
|
if (this.lastRenderedValue === null) {
|
|
@@ -1180,39 +1272,15 @@ export class GaugeChart extends BaseChart {
|
|
|
1180
1272
|
.style('pointer-events', 'all')
|
|
1181
1273
|
.style('cursor', 'pointer')
|
|
1182
1274
|
.on('mouseenter', (event) => {
|
|
1183
|
-
|
|
1184
|
-
if (!tooltipDiv) {
|
|
1185
|
-
return;
|
|
1186
|
-
}
|
|
1187
|
-
tooltipDiv
|
|
1188
|
-
.style('visibility', 'visible')
|
|
1189
|
-
.html(this.buildTooltipContent(progressColor));
|
|
1190
|
-
this.positionTooltip(event, tooltipDiv);
|
|
1275
|
+
this.showTooltipFromPointer(event, this.buildTooltipContent(progressColor));
|
|
1191
1276
|
})
|
|
1192
1277
|
.on('mousemove', (event) => {
|
|
1193
|
-
|
|
1194
|
-
if (!tooltipDiv) {
|
|
1195
|
-
return;
|
|
1196
|
-
}
|
|
1197
|
-
tooltipDiv
|
|
1198
|
-
.style('visibility', 'visible')
|
|
1199
|
-
.html(this.buildTooltipContent(progressColor));
|
|
1200
|
-
this.positionTooltip(event, tooltipDiv);
|
|
1278
|
+
this.showTooltipFromPointer(event, this.buildTooltipContent(progressColor));
|
|
1201
1279
|
})
|
|
1202
1280
|
.on('mouseleave', () => {
|
|
1203
|
-
|
|
1204
|
-
if (!tooltipDiv) {
|
|
1205
|
-
return;
|
|
1206
|
-
}
|
|
1207
|
-
tooltipDiv.style('visibility', 'hidden');
|
|
1281
|
+
this.hideTooltip();
|
|
1208
1282
|
});
|
|
1209
1283
|
}
|
|
1210
|
-
resolveTooltipDiv() {
|
|
1211
|
-
if (!this.tooltip) {
|
|
1212
|
-
return null;
|
|
1213
|
-
}
|
|
1214
|
-
return select(`#${this.tooltip.id}`);
|
|
1215
|
-
}
|
|
1216
1284
|
buildTooltipContent(progressColor) {
|
|
1217
1285
|
const payload = {
|
|
1218
1286
|
value: this.value,
|
|
@@ -1242,24 +1310,6 @@ export class GaugeChart extends BaseChart {
|
|
|
1242
1310
|
}
|
|
1243
1311
|
return content;
|
|
1244
1312
|
}
|
|
1245
|
-
positionTooltip(event, tooltipDiv) {
|
|
1246
|
-
const node = tooltipDiv.node();
|
|
1247
|
-
if (!node) {
|
|
1248
|
-
return;
|
|
1249
|
-
}
|
|
1250
|
-
const rect = node.getBoundingClientRect();
|
|
1251
|
-
let x = event.pageX + TOOLTIP_OFFSET_PX;
|
|
1252
|
-
let y = event.pageY - rect.height / 2;
|
|
1253
|
-
if (x + rect.width > window.innerWidth - EDGE_MARGIN_PX) {
|
|
1254
|
-
x = event.pageX - rect.width - TOOLTIP_OFFSET_PX;
|
|
1255
|
-
}
|
|
1256
|
-
x = Math.max(EDGE_MARGIN_PX, x);
|
|
1257
|
-
y = Math.max(EDGE_MARGIN_PX, Math.min(y, window.innerHeight +
|
|
1258
|
-
window.scrollY -
|
|
1259
|
-
rect.height -
|
|
1260
|
-
EDGE_MARGIN_PX));
|
|
1261
|
-
tooltipDiv.style('left', `${x}px`).style('top', `${y}px`);
|
|
1262
|
-
}
|
|
1263
1313
|
getLegendSeries() {
|
|
1264
1314
|
return this.segments.map((segment) => {
|
|
1265
1315
|
return {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type LazyMountableChart = {
|
|
2
|
+
destroy: () => void;
|
|
3
|
+
};
|
|
4
|
+
export type LazyChartFactory<TChart extends LazyMountableChart> = (container: HTMLElement) => TChart | Promise<TChart>;
|
|
5
|
+
export type LazyChartMountOptions = Pick<IntersectionObserverInit, 'root' | 'rootMargin' | 'threshold'> & {
|
|
6
|
+
onError?: (error: unknown) => void;
|
|
7
|
+
};
|
|
8
|
+
export type LazyChartMountHandle<TChart extends LazyMountableChart> = {
|
|
9
|
+
load: () => Promise<TChart | null>;
|
|
10
|
+
destroy: () => void;
|
|
11
|
+
getChart: () => TChart | null;
|
|
12
|
+
};
|
|
13
|
+
export declare function mountChartWhenVisible<TChart extends LazyMountableChart>(target: string | HTMLElement, factory: LazyChartFactory<TChart>, options?: LazyChartMountOptions): LazyChartMountHandle<TChart>;
|