@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/xy-chart.js
CHANGED
|
@@ -4,7 +4,19 @@ import { ChartValidationError, ChartValidator } from './validation.js';
|
|
|
4
4
|
import { GROUPED_GAP_TICK_PREFIX, GROUPED_GROUP_LABEL_KEY, } from './grouped-data.js';
|
|
5
5
|
import { resolveScaleValue } from './scale-utils.js';
|
|
6
6
|
import { mergeDeep } from './utils.js';
|
|
7
|
+
import { createXYMotionDriver, } from './xy-motion/driver.js';
|
|
8
|
+
import { createXYSeriesSnapshotId } from './xy-motion/helpers.js';
|
|
7
9
|
const DEFAULT_SERIES_COLOR = '#8884d8';
|
|
10
|
+
function resolveBarStackSettings(config) {
|
|
11
|
+
return {
|
|
12
|
+
mode: config.barStack?.mode ?? 'normal',
|
|
13
|
+
gap: config.barStack?.gap ?? 0.1,
|
|
14
|
+
reverseSeries: config.barStack?.reverseSeries ?? false,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function resolveAreaStackMode(config) {
|
|
18
|
+
return config.areaStack?.mode ?? 'none';
|
|
19
|
+
}
|
|
8
20
|
function isXYSeries(component) {
|
|
9
21
|
return (component.type === 'line' ||
|
|
10
22
|
component.type === 'scatter' ||
|
|
@@ -50,17 +62,25 @@ export class XYChart extends BaseChart {
|
|
|
50
62
|
writable: true,
|
|
51
63
|
value: void 0
|
|
52
64
|
});
|
|
65
|
+
Object.defineProperty(this, "motionDriver", {
|
|
66
|
+
enumerable: true,
|
|
67
|
+
configurable: true,
|
|
68
|
+
writable: true,
|
|
69
|
+
value: void 0
|
|
70
|
+
});
|
|
53
71
|
Object.defineProperty(this, "scaleConfigOverride", {
|
|
54
72
|
enumerable: true,
|
|
55
73
|
configurable: true,
|
|
56
74
|
writable: true,
|
|
57
75
|
value: null
|
|
58
76
|
});
|
|
77
|
+
const barStack = resolveBarStackSettings(config);
|
|
59
78
|
this.orientation = config.orientation ?? 'vertical';
|
|
60
|
-
this.barStackMode =
|
|
61
|
-
this.barStackGap =
|
|
62
|
-
this.barStackReverseSeries =
|
|
63
|
-
this.areaStackMode = config
|
|
79
|
+
this.barStackMode = barStack.mode;
|
|
80
|
+
this.barStackGap = barStack.gap;
|
|
81
|
+
this.barStackReverseSeries = barStack.reverseSeries;
|
|
82
|
+
this.areaStackMode = resolveAreaStackMode(config);
|
|
83
|
+
this.motionDriver = createXYMotionDriver(config.animate);
|
|
64
84
|
}
|
|
65
85
|
addChild(component) {
|
|
66
86
|
if (isXYSeries(component)) {
|
|
@@ -101,7 +121,15 @@ export class XYChart extends BaseChart {
|
|
|
101
121
|
areaStack: {
|
|
102
122
|
mode: this.areaStackMode,
|
|
103
123
|
},
|
|
124
|
+
animate: false,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
update(data) {
|
|
128
|
+
this.motionDriver.prepareForUpdate({
|
|
129
|
+
plotGroup: this.plotGroup,
|
|
130
|
+
visibleSeries: this.getVisibleSeries(),
|
|
104
131
|
});
|
|
132
|
+
super.update(data);
|
|
105
133
|
}
|
|
106
134
|
applyComponentOverrides(overrides) {
|
|
107
135
|
const restoreBase = super.applyComponentOverrides(overrides);
|
|
@@ -114,6 +142,7 @@ export class XYChart extends BaseChart {
|
|
|
114
142
|
prepareLayout(context) {
|
|
115
143
|
super.prepareLayout(context);
|
|
116
144
|
this.xAxis?.clearEstimatedSpace?.();
|
|
145
|
+
this.yAxis?.clearEstimatedSpace?.();
|
|
117
146
|
if (this.xAxis) {
|
|
118
147
|
const xKey = this.getXKey();
|
|
119
148
|
const labelKey = this.xAxis.labelKey;
|
|
@@ -125,24 +154,59 @@ export class XYChart extends BaseChart {
|
|
|
125
154
|
});
|
|
126
155
|
this.xAxis.estimateLayoutSpace?.(labels, this.renderTheme, context.svgNode);
|
|
127
156
|
}
|
|
157
|
+
if (this.yAxis) {
|
|
158
|
+
this.yAxis.estimateLayoutSpace?.(this.getYAxisEstimateLabels(), this.renderTheme, context.svgNode);
|
|
159
|
+
}
|
|
128
160
|
}
|
|
129
|
-
|
|
130
|
-
this.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
161
|
+
getYAxisEstimateLabels() {
|
|
162
|
+
if (!this.yAxis) {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
const yAxisConfig = this.yAxis.getExportConfig();
|
|
166
|
+
const { y: yConfig } = this.getResolvedAxisConfigs();
|
|
167
|
+
const tickFormat = yAxisConfig.tickFormat;
|
|
168
|
+
if (yConfig.type === 'band') {
|
|
169
|
+
const tickValues = this.resolveScaleDomain(yConfig, this.isHorizontalOrientation() ? this.getXKey() : null);
|
|
170
|
+
return tickValues.map((value) => {
|
|
171
|
+
if (typeof tickFormat === 'function') {
|
|
172
|
+
return tickFormat(value);
|
|
173
|
+
}
|
|
174
|
+
return String(value);
|
|
141
175
|
});
|
|
142
176
|
}
|
|
143
|
-
|
|
144
|
-
|
|
177
|
+
const scale = this.createContinuousScaleForLayoutEstimate(yConfig);
|
|
178
|
+
const tickValues = scale.ticks(5);
|
|
179
|
+
if (typeof tickFormat === 'function') {
|
|
180
|
+
return tickValues.map((value) => tickFormat(value));
|
|
181
|
+
}
|
|
182
|
+
const tickFormatter = scale.tickFormat(5, typeof tickFormat === 'string' ? tickFormat : undefined);
|
|
183
|
+
return tickValues.map((value) => tickFormatter(value));
|
|
184
|
+
}
|
|
185
|
+
createContinuousScaleForLayoutEstimate(config) {
|
|
186
|
+
const domain = this.resolveScaleDomain(config, null);
|
|
187
|
+
switch (config.type) {
|
|
188
|
+
case 'linear': {
|
|
189
|
+
const scale = scaleLinear()
|
|
190
|
+
.domain(domain)
|
|
191
|
+
.rangeRound([100, 0]);
|
|
192
|
+
return config.nice === false ? scale : scale.nice();
|
|
193
|
+
}
|
|
194
|
+
case 'time': {
|
|
195
|
+
const scale = scaleTime()
|
|
196
|
+
.domain(domain)
|
|
197
|
+
.range([100, 0]);
|
|
198
|
+
return config.nice === false ? scale : scale.nice();
|
|
199
|
+
}
|
|
200
|
+
case 'log': {
|
|
201
|
+
const scale = scaleLog()
|
|
202
|
+
.domain(domain)
|
|
203
|
+
.rangeRound([100, 0]);
|
|
204
|
+
return config.nice === false ? scale : scale.nice();
|
|
205
|
+
}
|
|
145
206
|
}
|
|
207
|
+
}
|
|
208
|
+
renderChart({ svg, plotGroup, plotArea, }) {
|
|
209
|
+
this.validateRenderState();
|
|
146
210
|
const xKey = this.getXKey();
|
|
147
211
|
const categoryScaleType = this.getCategoryScaleType();
|
|
148
212
|
const visibleSeries = this.getVisibleSeries();
|
|
@@ -151,21 +215,10 @@ export class XYChart extends BaseChart {
|
|
|
151
215
|
if (this.grid && this.x && this.y) {
|
|
152
216
|
this.grid.render(plotGroup, this.x, this.y, this.renderTheme, this.isHorizontalOrientation());
|
|
153
217
|
}
|
|
154
|
-
this.renderSeries(visibleSeries);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
if (this.yAxis) {
|
|
160
|
-
this.yAxis.render(svg, this.y, this.renderTheme, plotArea.left);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (this.tooltip && this.x && this.y) {
|
|
164
|
-
const visibleAreaSeries = visibleSeries.filter((series) => series.type === 'area');
|
|
165
|
-
const areaStackingContextBySeries = this.computeAreaStackingContexts(this.data, xKey, visibleAreaSeries);
|
|
166
|
-
this.tooltip.initialize(this.renderTheme);
|
|
167
|
-
this.tooltip.attachToArea(svg, this.data, visibleSeries, xKey, this.x, this.y, this.renderTheme, plotArea, this.parseValue.bind(this), this.isHorizontalOrientation(), categoryScaleType, (series, dataPoint) => this.getSeriesTooltipValue(series, dataPoint, xKey, areaStackingContextBySeries));
|
|
168
|
-
}
|
|
218
|
+
const motionResult = this.renderSeries(visibleSeries);
|
|
219
|
+
this.setReadyPromise(this.motionDriver.completeRender(motionResult));
|
|
220
|
+
this.renderAxes(svg, plotArea);
|
|
221
|
+
this.attachTooltip(svg, plotArea, visibleSeries, xKey, categoryScaleType);
|
|
169
222
|
this.renderInlineLegend(svg);
|
|
170
223
|
}
|
|
171
224
|
getXKey() {
|
|
@@ -487,6 +540,84 @@ export class XYChart extends BaseChart {
|
|
|
487
540
|
const scaleType = config.type;
|
|
488
541
|
const [rangeStart, rangeEnd] = this.getScaleRange(axis, scaleType, config.reverse ?? false);
|
|
489
542
|
const domain = this.resolveScaleDomain(config, dataKey);
|
|
543
|
+
this.validateExplicitBarValueDomain(config, dataKey, scaleType, domain);
|
|
544
|
+
this.validateScaleDomain(scaleType, domain);
|
|
545
|
+
return this.buildScale(scaleType, domain, [rangeStart, rangeEnd], config);
|
|
546
|
+
}
|
|
547
|
+
resolveScaleDomain(config, dataKey) {
|
|
548
|
+
const buckets = this.getSeriesBuckets();
|
|
549
|
+
const stackedAreaGroups = this.getStackedAreaGroups(buckets.areaSeries);
|
|
550
|
+
if (this.isBarValueScale(config.type, dataKey, buckets.barSeries) &&
|
|
551
|
+
config.type === 'log') {
|
|
552
|
+
throw new ChartValidationError('XYChart: bar series require a linear value axis because bars need a zero baseline');
|
|
553
|
+
}
|
|
554
|
+
const domain = this.resolveRawScaleDomain(config, dataKey, buckets, stackedAreaGroups);
|
|
555
|
+
return this.applyNiceDomain(config, domain);
|
|
556
|
+
}
|
|
557
|
+
getSeriesTooltipValue(series, dataPoint, xKey, areaStackingContextBySeries) {
|
|
558
|
+
const rawValue = dataPoint[series.dataKey];
|
|
559
|
+
if (rawValue === null || rawValue === undefined) {
|
|
560
|
+
return NaN;
|
|
561
|
+
}
|
|
562
|
+
const parsedValue = this.parseValue(rawValue);
|
|
563
|
+
if (!Number.isFinite(parsedValue)) {
|
|
564
|
+
return NaN;
|
|
565
|
+
}
|
|
566
|
+
if (series.type === 'bar') {
|
|
567
|
+
return series.getRenderedValue(parsedValue, this.orientation);
|
|
568
|
+
}
|
|
569
|
+
if (series.type !== 'area') {
|
|
570
|
+
return parsedValue;
|
|
571
|
+
}
|
|
572
|
+
return this.getAreaTooltipValue(dataPoint, xKey, parsedValue, areaStackingContextBySeries.get(series));
|
|
573
|
+
}
|
|
574
|
+
validateRenderState() {
|
|
575
|
+
this.validateSeriesOrientation();
|
|
576
|
+
this.validateSeriesData();
|
|
577
|
+
this.validateValueScaleRequirements();
|
|
578
|
+
this.validateXAxisDataKey();
|
|
579
|
+
}
|
|
580
|
+
validateSeriesData() {
|
|
581
|
+
this.series.forEach((series) => {
|
|
582
|
+
const typeName = this.getSeriesTypeName(series);
|
|
583
|
+
ChartValidator.validateDataKey(this.data, series.dataKey, typeName);
|
|
584
|
+
ChartValidator.validateNumericData(this.data, series.dataKey, typeName);
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
validateValueScaleRequirements() {
|
|
588
|
+
if ((this.resolvedScaleConfig.y?.type ?? 'linear') !== 'log') {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
this.series.forEach((series) => {
|
|
592
|
+
ChartValidator.validatePositiveData(this.data, series.dataKey, this.getSeriesTypeName(series));
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
validateXAxisDataKey() {
|
|
596
|
+
if (this.xAxis?.dataKey) {
|
|
597
|
+
ChartValidator.validateDataKey(this.data, this.xAxis.dataKey, 'XAxis');
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
renderAxes(svg, plotArea) {
|
|
601
|
+
if (!this.x || !this.y) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (this.xAxis) {
|
|
605
|
+
this.xAxis.render(svg, this.x, this.renderTheme, plotArea.bottom, this.data);
|
|
606
|
+
}
|
|
607
|
+
if (this.yAxis) {
|
|
608
|
+
this.yAxis.render(svg, this.y, this.renderTheme, plotArea.left);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
attachTooltip(svg, plotArea, visibleSeries, xKey, categoryScaleType) {
|
|
612
|
+
if (!this.tooltip || !this.x || !this.y) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const visibleAreaSeries = visibleSeries.filter((series) => series.type === 'area');
|
|
616
|
+
const areaStackingContextBySeries = this.computeAreaStackingContexts(this.data, xKey, visibleAreaSeries);
|
|
617
|
+
this.tooltip.initialize(this.renderTheme);
|
|
618
|
+
this.tooltip.attachToArea(svg, this.data, visibleSeries, xKey, this.x, this.y, this.renderTheme, plotArea, this.parseValue.bind(this), this.isHorizontalOrientation(), categoryScaleType, (series, dataPoint) => this.getSeriesTooltipValue(series, dataPoint, xKey, areaStackingContextBySeries));
|
|
619
|
+
}
|
|
620
|
+
validateExplicitBarValueDomain(config, dataKey, scaleType, domain) {
|
|
490
621
|
if (dataKey === null &&
|
|
491
622
|
this.series.some((series) => series.type === 'bar') &&
|
|
492
623
|
(scaleType === 'linear' || scaleType === 'log') &&
|
|
@@ -495,133 +626,154 @@ export class XYChart extends BaseChart {
|
|
|
495
626
|
config.max !== undefined)) {
|
|
496
627
|
ChartValidator.validateBarDomainIncludesZero(domain);
|
|
497
628
|
}
|
|
629
|
+
}
|
|
630
|
+
validateScaleDomain(scaleType, domain) {
|
|
498
631
|
if (scaleType === 'log') {
|
|
499
632
|
ChartValidator.validateScaleConfig(scaleType, domain);
|
|
500
633
|
}
|
|
634
|
+
}
|
|
635
|
+
buildScale(scaleType, domain, range, config) {
|
|
501
636
|
switch (scaleType) {
|
|
502
|
-
case 'band':
|
|
503
|
-
|
|
637
|
+
case 'band':
|
|
638
|
+
return scaleBand()
|
|
504
639
|
.domain(domain)
|
|
505
|
-
.rangeRound(
|
|
506
|
-
|
|
507
|
-
scale.paddingInner(config.padding);
|
|
508
|
-
}
|
|
509
|
-
else {
|
|
510
|
-
scale.paddingInner(0.1);
|
|
511
|
-
}
|
|
512
|
-
return scale;
|
|
513
|
-
}
|
|
640
|
+
.rangeRound(range)
|
|
641
|
+
.paddingInner(config.padding ?? 0.1);
|
|
514
642
|
case 'linear': {
|
|
515
643
|
const scale = scaleLinear()
|
|
516
644
|
.domain(domain)
|
|
517
|
-
.rangeRound(
|
|
645
|
+
.rangeRound(range);
|
|
518
646
|
return config.nice === false ? scale : scale.nice();
|
|
519
647
|
}
|
|
520
648
|
case 'time': {
|
|
521
649
|
const scale = scaleTime()
|
|
522
650
|
.domain(domain)
|
|
523
|
-
.range(
|
|
651
|
+
.range(range);
|
|
524
652
|
return config.nice === false ? scale : scale.nice();
|
|
525
653
|
}
|
|
526
654
|
case 'log': {
|
|
527
655
|
const scale = scaleLog()
|
|
528
656
|
.domain(domain)
|
|
529
|
-
.rangeRound(
|
|
657
|
+
.rangeRound(range);
|
|
530
658
|
return config.nice === false ? scale : scale.nice();
|
|
531
659
|
}
|
|
532
660
|
default:
|
|
533
661
|
throw new Error(`Unsupported scale type: ${scaleType}`);
|
|
534
662
|
}
|
|
535
663
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
664
|
+
getSeriesBuckets() {
|
|
665
|
+
return {
|
|
666
|
+
barSeries: this.series.filter((s) => s.type === 'bar'),
|
|
667
|
+
lineSeries: this.series.filter((s) => s.type === 'line'),
|
|
668
|
+
scatterSeries: this.series.filter((s) => s.type === 'scatter'),
|
|
669
|
+
areaSeries: this.series.filter((s) => s.type === 'area'),
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
isBarValueScale(scaleType, dataKey, barSeries) {
|
|
673
|
+
return (dataKey === null &&
|
|
543
674
|
barSeries.length > 0 &&
|
|
544
|
-
(
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
throw new ChartValidationError('XYChart: bar series require a linear value axis because bars need a zero baseline');
|
|
548
|
-
}
|
|
675
|
+
(scaleType === 'linear' || scaleType === 'log'));
|
|
676
|
+
}
|
|
677
|
+
resolveRawScaleDomain(config, dataKey, buckets, stackedAreaGroups) {
|
|
549
678
|
if (config.domain) {
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
else if (config.type === 'band' && dataKey) {
|
|
553
|
-
domain = this.buildBandDomainWithGroupGaps(dataKey, config.groupGap ?? 0);
|
|
554
|
-
}
|
|
555
|
-
else if (config.type === 'time' && dataKey) {
|
|
556
|
-
domain = [
|
|
557
|
-
min(this.data, (d) => resolveScaleValue(d[dataKey], 'time')),
|
|
558
|
-
max(this.data, (d) => resolveScaleValue(d[dataKey], 'time')),
|
|
559
|
-
];
|
|
560
|
-
}
|
|
561
|
-
else if ((config.type === 'linear' || config.type === 'log') &&
|
|
562
|
-
dataKey) {
|
|
563
|
-
const values = this.data
|
|
564
|
-
.map((d) => this.parseValue(d[dataKey]))
|
|
565
|
-
.filter((value) => Number.isFinite(value));
|
|
566
|
-
const minVal = config.min ?? min(values) ?? 0;
|
|
567
|
-
const maxVal = config.max ?? max(values) ?? 100;
|
|
568
|
-
domain = [minVal, maxVal];
|
|
679
|
+
return config.domain;
|
|
569
680
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
const hasPercentAreas = this.areaStackMode === 'percent' && stackedAreaGroups.size > 0;
|
|
573
|
-
if (hasPercentBars || hasPercentAreas) {
|
|
574
|
-
let minDomain = 0;
|
|
575
|
-
let maxDomain = 100;
|
|
576
|
-
if (hasPercentBars) {
|
|
577
|
-
[minDomain, maxDomain] = this.getBarValueDomain(this.getXKey(), barSeries);
|
|
578
|
-
}
|
|
579
|
-
if (hasPercentAreas) {
|
|
580
|
-
minDomain = Math.min(minDomain, 0);
|
|
581
|
-
maxDomain = Math.max(maxDomain, 100);
|
|
582
|
-
}
|
|
583
|
-
domain = [minDomain, maxDomain];
|
|
584
|
-
}
|
|
585
|
-
else {
|
|
586
|
-
const values = this.collectSeriesValues([
|
|
587
|
-
...lineSeries,
|
|
588
|
-
...scatterSeries,
|
|
589
|
-
...areaSeries,
|
|
590
|
-
]);
|
|
591
|
-
const stackedValues = [];
|
|
592
|
-
const minCandidates = [...values];
|
|
593
|
-
const maxCandidates = [...values];
|
|
594
|
-
if (barSeries.length > 0) {
|
|
595
|
-
const [barMin, barMax] = this.getBarValueDomain(this.getXKey(), barSeries);
|
|
596
|
-
minCandidates.push(barMin);
|
|
597
|
-
maxCandidates.push(barMax);
|
|
598
|
-
}
|
|
599
|
-
const stackedAreaKeys = new Set();
|
|
600
|
-
stackedAreaGroups.forEach((stackSeries) => {
|
|
601
|
-
stackSeries.forEach((series) => {
|
|
602
|
-
stackedAreaKeys.add(series.dataKey);
|
|
603
|
-
});
|
|
604
|
-
this.data.forEach((dataPoint) => {
|
|
605
|
-
const total = stackSeries.reduce((sum, series) => {
|
|
606
|
-
const value = this.parseValue(dataPoint[series.dataKey]);
|
|
607
|
-
return Number.isFinite(value) ? sum + value : sum;
|
|
608
|
-
}, 0);
|
|
609
|
-
stackedValues.push(total);
|
|
610
|
-
});
|
|
611
|
-
minCandidates.push(0);
|
|
612
|
-
});
|
|
613
|
-
areaSeries
|
|
614
|
-
.filter((series) => !stackedAreaKeys.has(series.dataKey))
|
|
615
|
-
.forEach((series) => {
|
|
616
|
-
minCandidates.push(series.baseline);
|
|
617
|
-
});
|
|
618
|
-
const minVal = config.min ?? min(minCandidates) ?? 0;
|
|
619
|
-
const maxVal = config.max ??
|
|
620
|
-
max([...maxCandidates, ...stackedValues]) ??
|
|
621
|
-
100;
|
|
622
|
-
domain = [minVal, maxVal];
|
|
623
|
-
}
|
|
681
|
+
if (config.type === 'band' && dataKey) {
|
|
682
|
+
return this.buildBandDomainWithGroupGaps(dataKey, config.groupGap ?? 0);
|
|
624
683
|
}
|
|
684
|
+
if (config.type === 'time' && dataKey) {
|
|
685
|
+
return this.resolveTimeDomain(dataKey);
|
|
686
|
+
}
|
|
687
|
+
if ((config.type === 'linear' || config.type === 'log') && dataKey) {
|
|
688
|
+
return this.resolveNumericDataKeyDomain(config, dataKey);
|
|
689
|
+
}
|
|
690
|
+
return this.resolveSeriesValueDomain(config, buckets, stackedAreaGroups);
|
|
691
|
+
}
|
|
692
|
+
resolveTimeDomain(dataKey) {
|
|
693
|
+
return [
|
|
694
|
+
min(this.data, (d) => resolveScaleValue(d[dataKey], 'time')),
|
|
695
|
+
max(this.data, (d) => resolveScaleValue(d[dataKey], 'time')),
|
|
696
|
+
];
|
|
697
|
+
}
|
|
698
|
+
resolveNumericDataKeyDomain(config, dataKey) {
|
|
699
|
+
const values = this.data
|
|
700
|
+
.map((d) => this.parseValue(d[dataKey]))
|
|
701
|
+
.filter((value) => Number.isFinite(value));
|
|
702
|
+
return [
|
|
703
|
+
config.min ?? min(values) ?? 0,
|
|
704
|
+
config.max ?? max(values) ?? 100,
|
|
705
|
+
];
|
|
706
|
+
}
|
|
707
|
+
resolveSeriesValueDomain(config, buckets, stackedAreaGroups) {
|
|
708
|
+
if (this.hasPercentValueDomain(buckets.barSeries, stackedAreaGroups)) {
|
|
709
|
+
return this.resolvePercentValueDomain(buckets.barSeries, stackedAreaGroups);
|
|
710
|
+
}
|
|
711
|
+
return this.resolveStandardValueDomain(config, buckets, stackedAreaGroups);
|
|
712
|
+
}
|
|
713
|
+
hasPercentValueDomain(barSeries, stackedAreaGroups) {
|
|
714
|
+
return ((this.barStackMode === 'percent' && barSeries.length > 0) ||
|
|
715
|
+
(this.areaStackMode === 'percent' && stackedAreaGroups.size > 0));
|
|
716
|
+
}
|
|
717
|
+
resolvePercentValueDomain(barSeries, stackedAreaGroups) {
|
|
718
|
+
let minDomain = 0;
|
|
719
|
+
let maxDomain = 100;
|
|
720
|
+
if (this.barStackMode === 'percent' && barSeries.length > 0) {
|
|
721
|
+
[minDomain, maxDomain] = this.getBarValueDomain(this.getXKey(), barSeries);
|
|
722
|
+
}
|
|
723
|
+
if (this.areaStackMode === 'percent' && stackedAreaGroups.size > 0) {
|
|
724
|
+
minDomain = Math.min(minDomain, 0);
|
|
725
|
+
maxDomain = Math.max(maxDomain, 100);
|
|
726
|
+
}
|
|
727
|
+
return [minDomain, maxDomain];
|
|
728
|
+
}
|
|
729
|
+
resolveStandardValueDomain(config, buckets, stackedAreaGroups) {
|
|
730
|
+
const values = this.collectSeriesValues([
|
|
731
|
+
...buckets.lineSeries,
|
|
732
|
+
...buckets.scatterSeries,
|
|
733
|
+
...buckets.areaSeries,
|
|
734
|
+
]);
|
|
735
|
+
const minCandidates = [...values];
|
|
736
|
+
const maxCandidates = [...values];
|
|
737
|
+
const stackedValues = this.collectStackedAreaTotals(stackedAreaGroups);
|
|
738
|
+
if (buckets.barSeries.length > 0) {
|
|
739
|
+
const [barMin, barMax] = this.getBarValueDomain(this.getXKey(), buckets.barSeries);
|
|
740
|
+
minCandidates.push(barMin);
|
|
741
|
+
maxCandidates.push(barMax);
|
|
742
|
+
}
|
|
743
|
+
this.addAreaBaselines(minCandidates, buckets.areaSeries, stackedAreaGroups);
|
|
744
|
+
return [
|
|
745
|
+
config.min ?? min(minCandidates) ?? 0,
|
|
746
|
+
config.max ?? max([...maxCandidates, ...stackedValues]) ?? 100,
|
|
747
|
+
];
|
|
748
|
+
}
|
|
749
|
+
collectStackedAreaTotals(stackedAreaGroups) {
|
|
750
|
+
const stackedValues = [];
|
|
751
|
+
stackedAreaGroups.forEach((stackSeries) => {
|
|
752
|
+
this.data.forEach((dataPoint) => {
|
|
753
|
+
const total = stackSeries.reduce((sum, series) => {
|
|
754
|
+
const value = this.parseValue(dataPoint[series.dataKey]);
|
|
755
|
+
return Number.isFinite(value) ? sum + value : sum;
|
|
756
|
+
}, 0);
|
|
757
|
+
stackedValues.push(total);
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
return stackedValues;
|
|
761
|
+
}
|
|
762
|
+
addAreaBaselines(minCandidates, areaSeries, stackedAreaGroups) {
|
|
763
|
+
const stackedAreaKeys = new Set();
|
|
764
|
+
stackedAreaGroups.forEach((stackSeries) => {
|
|
765
|
+
stackSeries.forEach((series) => {
|
|
766
|
+
stackedAreaKeys.add(series.dataKey);
|
|
767
|
+
});
|
|
768
|
+
minCandidates.push(0);
|
|
769
|
+
});
|
|
770
|
+
areaSeries
|
|
771
|
+
.filter((series) => !stackedAreaKeys.has(series.dataKey))
|
|
772
|
+
.forEach((series) => {
|
|
773
|
+
minCandidates.push(series.baseline);
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
applyNiceDomain(config, domain) {
|
|
625
777
|
if (config.nice === false) {
|
|
626
778
|
return domain;
|
|
627
779
|
}
|
|
@@ -645,22 +797,7 @@ export class XYChart extends BaseChart {
|
|
|
645
797
|
}
|
|
646
798
|
return domain;
|
|
647
799
|
}
|
|
648
|
-
|
|
649
|
-
const rawValue = dataPoint[series.dataKey];
|
|
650
|
-
if (rawValue === null || rawValue === undefined) {
|
|
651
|
-
return NaN;
|
|
652
|
-
}
|
|
653
|
-
const parsedValue = this.parseValue(rawValue);
|
|
654
|
-
if (!Number.isFinite(parsedValue)) {
|
|
655
|
-
return NaN;
|
|
656
|
-
}
|
|
657
|
-
if (series.type !== 'area') {
|
|
658
|
-
if (series.type === 'bar') {
|
|
659
|
-
return series.getRenderedValue(parsedValue, this.orientation);
|
|
660
|
-
}
|
|
661
|
-
return parsedValue;
|
|
662
|
-
}
|
|
663
|
-
const stackingContext = areaStackingContextBySeries.get(series);
|
|
800
|
+
getAreaTooltipValue(dataPoint, xKey, parsedValue, stackingContext) {
|
|
664
801
|
if (!stackingContext || stackingContext.mode === 'none') {
|
|
665
802
|
return parsedValue;
|
|
666
803
|
}
|
|
@@ -677,7 +814,10 @@ export class XYChart extends BaseChart {
|
|
|
677
814
|
}
|
|
678
815
|
renderSeries(visibleSeries) {
|
|
679
816
|
if (!this.plotGroup || !this.x || !this.y) {
|
|
680
|
-
return
|
|
817
|
+
return {
|
|
818
|
+
snapshotCollection: new Map(),
|
|
819
|
+
transitions: [],
|
|
820
|
+
};
|
|
681
821
|
}
|
|
682
822
|
const xKey = this.getXKey();
|
|
683
823
|
const categoryScaleType = this.getCategoryScaleType();
|
|
@@ -692,6 +832,8 @@ export class XYChart extends BaseChart {
|
|
|
692
832
|
: null;
|
|
693
833
|
const { cumulativeDataBySeriesIndex, positiveCumulativeDataBySeriesIndex, negativeCumulativeDataBySeriesIndex, totalData, positiveTotalData, negativeTotalData, rawValuesBySeriesIndex, } = this.computeStackingData(this.data, xKey, barSeries);
|
|
694
834
|
const areaStackingContextBySeries = this.computeAreaStackingContexts(this.data, xKey, areaSeries);
|
|
835
|
+
const snapshotCollection = new Map();
|
|
836
|
+
const transitions = [];
|
|
695
837
|
barSeries.forEach((series, barIndex) => {
|
|
696
838
|
const nextLayerData = this.barStackMode === 'layer'
|
|
697
839
|
? rawValuesBySeriesIndex.get(barIndex + 1)
|
|
@@ -711,20 +853,83 @@ export class XYChart extends BaseChart {
|
|
|
711
853
|
gap: this.barStackGap,
|
|
712
854
|
nextLayerData,
|
|
713
855
|
};
|
|
714
|
-
series.render(this.plotGroup, this.data, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.renderTheme, stackingContext, this.orientation);
|
|
856
|
+
const result = series.render(this.plotGroup, this.data, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.renderTheme, stackingContext, this.orientation, this.motionDriver.getBarAnimationContext(series, this.getBarBaselinePosition()));
|
|
857
|
+
snapshotCollection.set(createXYSeriesSnapshotId(series.type, series.dataKey), result.snapshot);
|
|
858
|
+
transitions.push(...result.transitions);
|
|
715
859
|
});
|
|
716
860
|
areaSeries.forEach((series) => {
|
|
717
|
-
series.render(this.plotGroup, this.data, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.renderTheme, areaStackingContextBySeries.get(series), areaValueLabelLayer ?? undefined);
|
|
861
|
+
const result = series.render(this.plotGroup, this.data, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.renderTheme, areaStackingContextBySeries.get(series), areaValueLabelLayer ?? undefined, this.motionDriver.getAreaAnimationContext(series));
|
|
862
|
+
snapshotCollection.set(createXYSeriesSnapshotId(series.type, series.dataKey), result.snapshot);
|
|
863
|
+
transitions.push(...result.transitions);
|
|
718
864
|
});
|
|
719
865
|
lineSeries.forEach((series) => {
|
|
720
|
-
series.render(this.plotGroup, this.data, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.renderTheme);
|
|
866
|
+
const result = series.render(this.plotGroup, this.data, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.renderTheme, this.motionDriver.getPointAnimationContext(series, this.getPointBaselineY()));
|
|
867
|
+
snapshotCollection.set(createXYSeriesSnapshotId(series.type, series.dataKey), result.snapshot);
|
|
868
|
+
transitions.push(...result.transitions);
|
|
721
869
|
});
|
|
722
870
|
scatterSeries.forEach((series) => {
|
|
723
|
-
series.render(this.plotGroup, this.data, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.renderTheme);
|
|
871
|
+
const result = series.render(this.plotGroup, this.data, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.renderTheme, this.motionDriver.getPointAnimationContext(series, this.getPointBaselineY()));
|
|
872
|
+
snapshotCollection.set(createXYSeriesSnapshotId(series.type, series.dataKey), result.snapshot);
|
|
873
|
+
transitions.push(...result.transitions);
|
|
724
874
|
});
|
|
725
875
|
if (areaValueLabelLayer) {
|
|
726
876
|
areaValueLabelLayer.raise();
|
|
727
877
|
}
|
|
878
|
+
return {
|
|
879
|
+
snapshotCollection,
|
|
880
|
+
transitions,
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
getBarBaselinePosition() {
|
|
884
|
+
const valueScale = this.isHorizontalOrientation() ? this.x : this.y;
|
|
885
|
+
if (!valueScale) {
|
|
886
|
+
return 0;
|
|
887
|
+
}
|
|
888
|
+
return valueScale(0) ?? 0;
|
|
889
|
+
}
|
|
890
|
+
getPointBaselineY() {
|
|
891
|
+
if (!this.y) {
|
|
892
|
+
return 0;
|
|
893
|
+
}
|
|
894
|
+
const numericDomain = this.getNumericPointBaselineDomain();
|
|
895
|
+
if (!numericDomain) {
|
|
896
|
+
return this.getPointBaselineFallback();
|
|
897
|
+
}
|
|
898
|
+
const baselineValue = this.getPointBaselineValue(numericDomain);
|
|
899
|
+
return (this.y(baselineValue) ?? this.getPointBaselineFallback());
|
|
900
|
+
}
|
|
901
|
+
getNumericPointBaselineDomain() {
|
|
902
|
+
if (!this.y) {
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
const domain = this.y.domain?.();
|
|
906
|
+
if (!Array.isArray(domain) ||
|
|
907
|
+
domain.length < 2 ||
|
|
908
|
+
typeof domain[0] !== 'number' ||
|
|
909
|
+
typeof domain[1] !== 'number') {
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
return [domain[0], domain[1]];
|
|
913
|
+
}
|
|
914
|
+
getPointBaselineValue(domain) {
|
|
915
|
+
const minValue = Math.min(...domain);
|
|
916
|
+
const maxValue = Math.max(...domain);
|
|
917
|
+
if (minValue <= 0 && maxValue >= 0) {
|
|
918
|
+
return 0;
|
|
919
|
+
}
|
|
920
|
+
return Math.abs(domain[0]) <= Math.abs(domain[1])
|
|
921
|
+
? domain[0]
|
|
922
|
+
: domain[1];
|
|
923
|
+
}
|
|
924
|
+
getPointBaselineFallback() {
|
|
925
|
+
if (!this.y) {
|
|
926
|
+
return 0;
|
|
927
|
+
}
|
|
928
|
+
const range = this.y.range?.();
|
|
929
|
+
if (!Array.isArray(range) || range.length === 0) {
|
|
930
|
+
return 0;
|
|
931
|
+
}
|
|
932
|
+
return Math.max(...range);
|
|
728
933
|
}
|
|
729
934
|
computeStackingData(data, xKey, barSeries) {
|
|
730
935
|
const cumulativeDataBySeriesIndex = new Map();
|