@internetstiftelsen/charts 0.9.2 → 0.10.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 +137 -3
- package/dist/area.d.ts +2 -0
- package/dist/area.js +39 -31
- package/dist/bar.d.ts +20 -1
- package/dist/bar.js +395 -519
- package/dist/base-chart.d.ts +21 -1
- package/dist/base-chart.js +166 -93
- package/dist/chart-group.d.ts +137 -0
- package/dist/chart-group.js +1155 -0
- package/dist/chart-interface.d.ts +1 -1
- package/dist/donut-center-content.d.ts +1 -0
- package/dist/donut-center-content.js +21 -38
- package/dist/donut-chart.js +30 -15
- package/dist/gauge-chart.d.ts +20 -0
- package/dist/gauge-chart.js +229 -133
- package/dist/legend-state.d.ts +19 -0
- package/dist/legend-state.js +81 -0
- package/dist/legend.d.ts +5 -2
- package/dist/legend.js +45 -38
- package/dist/line.js +3 -1
- package/dist/pie-chart.d.ts +3 -0
- package/dist/pie-chart.js +45 -19
- package/dist/scatter.d.ts +16 -0
- package/dist/scatter.js +165 -0
- package/dist/tooltip.d.ts +2 -1
- package/dist/tooltip.js +21 -25
- package/dist/types.d.ts +19 -1
- package/dist/utils.js +11 -19
- package/dist/validation.d.ts +4 -0
- package/dist/validation.js +19 -0
- package/dist/x-axis.d.ts +10 -0
- package/dist/x-axis.js +190 -149
- package/dist/xy-chart.d.ts +40 -1
- package/dist/xy-chart.js +488 -165
- package/dist/y-axis.d.ts +7 -2
- package/dist/y-axis.js +99 -10
- package/docs/chart-group.md +213 -0
- package/docs/components.md +321 -0
- package/docs/donut-chart.md +193 -0
- package/docs/gauge-chart.md +175 -0
- package/docs/getting-started.md +311 -0
- package/docs/pie-chart.md +123 -0
- package/docs/theming.md +162 -0
- package/docs/word-cloud-chart.md +98 -0
- package/docs/xy-chart.md +517 -0
- package/package.json +6 -4
package/dist/base-chart.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type { ChartData, DataItem, ChartTheme, ResolvedChartTheme, AxisScaleConfig, ExportFormat, ExportOptions, D3Scale, ExportHookContext, ExportRenderContext, LegendItem, LegendSeries, ResponsiveConfig, ResponsiveRenderContext, DeepPartial } from './types.js';
|
|
2
|
+
import type { ChartData, DataItem, ChartTheme, ResolvedChartTheme, AxisScaleConfig, ExportFormat, ExportOptions, D3Scale, ExportHookContext, ExportRenderContext, LegendItem, LegendMode, LegendSeries, ResponsiveConfig, ResponsiveRenderContext, DeepPartial } from './types.js';
|
|
3
3
|
import type { ChartComponentBase, LayoutAwareComponentBase } from './chart-interface.js';
|
|
4
4
|
import type { XAxis } from './x-axis.js';
|
|
5
5
|
import type { YAxis } from './y-axis.js';
|
|
@@ -8,6 +8,7 @@ import type { Tooltip } from './tooltip.js';
|
|
|
8
8
|
import type { Legend } from './legend.js';
|
|
9
9
|
import type { Title } from './title.js';
|
|
10
10
|
import { LayoutManager, type PlotAreaBounds } from './layout-manager.js';
|
|
11
|
+
import { LegendStateController } from './legend-state.js';
|
|
11
12
|
type VisualExportFormat = 'svg' | 'png' | 'jpg' | 'pdf';
|
|
12
13
|
type RenderDimensions = {
|
|
13
14
|
width: number;
|
|
@@ -82,10 +83,13 @@ export declare abstract class BaseChart {
|
|
|
82
83
|
protected resizeObserver: ResizeObserver | null;
|
|
83
84
|
protected layoutManager: LayoutManager;
|
|
84
85
|
protected plotArea: PlotAreaBounds | null;
|
|
86
|
+
protected readonly legendState: LegendStateController;
|
|
85
87
|
private readyPromise;
|
|
86
88
|
private disconnectedLegendContainer;
|
|
87
89
|
private renderThemeOverride;
|
|
88
90
|
private renderSizeOverride;
|
|
91
|
+
private readonly renderCallbacks;
|
|
92
|
+
private legendModeOverride;
|
|
89
93
|
protected constructor(config: BaseChartConfig);
|
|
90
94
|
/**
|
|
91
95
|
* Adds a component (axis, grid, tooltip, etc.) to the chart
|
|
@@ -101,6 +105,7 @@ export declare abstract class BaseChart {
|
|
|
101
105
|
*/
|
|
102
106
|
private performRender;
|
|
103
107
|
protected resolveRenderDimensions(containerRect: DOMRect): RenderDimensions;
|
|
108
|
+
private pushIfIncluded;
|
|
104
109
|
private resolveAccessibleLabel;
|
|
105
110
|
private syncAccessibleLabelFromSvg;
|
|
106
111
|
protected resolveResponsiveContext(context: {
|
|
@@ -112,6 +117,17 @@ export declare abstract class BaseChart {
|
|
|
112
117
|
private applyRenderTheme;
|
|
113
118
|
protected get renderTheme(): ChartTheme;
|
|
114
119
|
protected get resolvedRenderTheme(): ResolvedChartTheme;
|
|
120
|
+
protected getLegendMode(): LegendMode | null;
|
|
121
|
+
protected hasInlineLegend(): boolean;
|
|
122
|
+
protected hasDisconnectedLegend(): boolean;
|
|
123
|
+
protected shouldIncludeLegendInExport(): boolean;
|
|
124
|
+
getConfiguredSize(): {
|
|
125
|
+
width?: number;
|
|
126
|
+
height?: number;
|
|
127
|
+
};
|
|
128
|
+
setLegendModeOverride(mode: LegendMode | null, rerender?: boolean): this;
|
|
129
|
+
onRender(callback: () => void): () => void;
|
|
130
|
+
private notifyRendered;
|
|
115
131
|
/**
|
|
116
132
|
* Get layout-aware components in order
|
|
117
133
|
* Override in subclasses to provide chart-specific components
|
|
@@ -193,6 +209,10 @@ export declare abstract class BaseChart {
|
|
|
193
209
|
private exportImage;
|
|
194
210
|
private exportPDF;
|
|
195
211
|
protected exportSVG(options?: ExportOptions, formatForHooks?: VisualExportFormat): Promise<string>;
|
|
212
|
+
private requireRenderedSvg;
|
|
213
|
+
private resolveExportContext;
|
|
214
|
+
private createExportSvgClone;
|
|
215
|
+
private populateExportChart;
|
|
196
216
|
protected exportJSON(): string;
|
|
197
217
|
}
|
|
198
218
|
export {};
|
package/dist/base-chart.js
CHANGED
|
@@ -7,6 +7,7 @@ import { exportRasterBlob } from './export-image.js';
|
|
|
7
7
|
import { exportXLSXBlob } from './export-xlsx.js';
|
|
8
8
|
import { exportPDFBlob } from './export-pdf.js';
|
|
9
9
|
import { normalizeChartData } from './grouped-data.js';
|
|
10
|
+
import { LegendStateController } from './legend-state.js';
|
|
10
11
|
import { mergeDeep } from './utils.js';
|
|
11
12
|
function normalizeChartDimension(value, name) {
|
|
12
13
|
if (value === undefined) {
|
|
@@ -182,6 +183,12 @@ export class BaseChart {
|
|
|
182
183
|
writable: true,
|
|
183
184
|
value: null
|
|
184
185
|
});
|
|
186
|
+
Object.defineProperty(this, "legendState", {
|
|
187
|
+
enumerable: true,
|
|
188
|
+
configurable: true,
|
|
189
|
+
writable: true,
|
|
190
|
+
value: void 0
|
|
191
|
+
});
|
|
185
192
|
Object.defineProperty(this, "readyPromise", {
|
|
186
193
|
enumerable: true,
|
|
187
194
|
configurable: true,
|
|
@@ -206,6 +213,18 @@ export class BaseChart {
|
|
|
206
213
|
writable: true,
|
|
207
214
|
value: null
|
|
208
215
|
});
|
|
216
|
+
Object.defineProperty(this, "renderCallbacks", {
|
|
217
|
+
enumerable: true,
|
|
218
|
+
configurable: true,
|
|
219
|
+
writable: true,
|
|
220
|
+
value: new Set()
|
|
221
|
+
});
|
|
222
|
+
Object.defineProperty(this, "legendModeOverride", {
|
|
223
|
+
enumerable: true,
|
|
224
|
+
configurable: true,
|
|
225
|
+
writable: true,
|
|
226
|
+
value: null
|
|
227
|
+
});
|
|
209
228
|
const normalized = normalizeChartData(config.data);
|
|
210
229
|
ChartValidator.validateData(normalized.data);
|
|
211
230
|
this.sourceData = config.data;
|
|
@@ -217,6 +236,10 @@ export class BaseChart {
|
|
|
217
236
|
this.height = this.configuredHeight ?? DEFAULT_CHART_HEIGHT;
|
|
218
237
|
this.scaleConfig = config.scales || {};
|
|
219
238
|
this.responsiveConfig = config.responsive;
|
|
239
|
+
this.legendState = new LegendStateController();
|
|
240
|
+
this.legendState.subscribe(() => {
|
|
241
|
+
this.rerender();
|
|
242
|
+
});
|
|
220
243
|
this.layoutManager = new LayoutManager(this.resolvedRenderTheme);
|
|
221
244
|
}
|
|
222
245
|
/**
|
|
@@ -309,6 +332,7 @@ export class BaseChart {
|
|
|
309
332
|
plotArea,
|
|
310
333
|
});
|
|
311
334
|
this.renderDisconnectedLegend();
|
|
335
|
+
this.notifyRendered();
|
|
312
336
|
}
|
|
313
337
|
finally {
|
|
314
338
|
restoreComponents();
|
|
@@ -316,22 +340,34 @@ export class BaseChart {
|
|
|
316
340
|
}
|
|
317
341
|
}
|
|
318
342
|
resolveRenderDimensions(containerRect) {
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
(containerRect.height || DEFAULT_CHART_HEIGHT);
|
|
325
|
-
return {
|
|
326
|
-
width,
|
|
327
|
-
height,
|
|
328
|
-
svgWidthAttr: this.renderSizeOverride?.width ??
|
|
329
|
-
this.configuredWidth ??
|
|
330
|
-
'100%',
|
|
331
|
-
svgHeightAttr: this.renderSizeOverride?.height ??
|
|
332
|
-
this.configuredHeight ??
|
|
333
|
-
'100%',
|
|
343
|
+
const dimensions = {
|
|
344
|
+
width: containerRect.width || DEFAULT_CHART_WIDTH,
|
|
345
|
+
height: containerRect.height || DEFAULT_CHART_HEIGHT,
|
|
346
|
+
svgWidthAttr: '100%',
|
|
347
|
+
svgHeightAttr: '100%',
|
|
334
348
|
};
|
|
349
|
+
if (this.configuredWidth !== undefined) {
|
|
350
|
+
dimensions.width = this.configuredWidth;
|
|
351
|
+
dimensions.svgWidthAttr = this.configuredWidth;
|
|
352
|
+
}
|
|
353
|
+
if (this.configuredHeight !== undefined) {
|
|
354
|
+
dimensions.height = this.configuredHeight;
|
|
355
|
+
dimensions.svgHeightAttr = this.configuredHeight;
|
|
356
|
+
}
|
|
357
|
+
if (this.renderSizeOverride?.width !== undefined) {
|
|
358
|
+
dimensions.width = this.renderSizeOverride.width;
|
|
359
|
+
dimensions.svgWidthAttr = this.renderSizeOverride.width;
|
|
360
|
+
}
|
|
361
|
+
if (this.renderSizeOverride?.height !== undefined) {
|
|
362
|
+
dimensions.height = this.renderSizeOverride.height;
|
|
363
|
+
dimensions.svgHeightAttr = this.renderSizeOverride.height;
|
|
364
|
+
}
|
|
365
|
+
return dimensions;
|
|
366
|
+
}
|
|
367
|
+
pushIfIncluded(components, shouldInclude, component) {
|
|
368
|
+
if (shouldInclude && component) {
|
|
369
|
+
components.push(component);
|
|
370
|
+
}
|
|
335
371
|
}
|
|
336
372
|
resolveAccessibleLabel() {
|
|
337
373
|
const titleText = this.title?.text.trim();
|
|
@@ -401,6 +437,49 @@ export class BaseChart {
|
|
|
401
437
|
height: this.height,
|
|
402
438
|
};
|
|
403
439
|
}
|
|
440
|
+
getLegendMode() {
|
|
441
|
+
if (!this.legend) {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
return this.legendModeOverride ?? this.legend.mode;
|
|
445
|
+
}
|
|
446
|
+
hasInlineLegend() {
|
|
447
|
+
return this.getLegendMode() === 'inline';
|
|
448
|
+
}
|
|
449
|
+
hasDisconnectedLegend() {
|
|
450
|
+
return this.getLegendMode() === 'disconnected';
|
|
451
|
+
}
|
|
452
|
+
shouldIncludeLegendInExport() {
|
|
453
|
+
const legendMode = this.getLegendMode();
|
|
454
|
+
return !!this.legend && legendMode !== null && legendMode !== 'hidden';
|
|
455
|
+
}
|
|
456
|
+
getConfiguredSize() {
|
|
457
|
+
return {
|
|
458
|
+
width: this.configuredWidth,
|
|
459
|
+
height: this.configuredHeight,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
setLegendModeOverride(mode, rerender = true) {
|
|
463
|
+
if (this.legendModeOverride === mode) {
|
|
464
|
+
return this;
|
|
465
|
+
}
|
|
466
|
+
this.legendModeOverride = mode;
|
|
467
|
+
if (rerender) {
|
|
468
|
+
this.rerender();
|
|
469
|
+
}
|
|
470
|
+
return this;
|
|
471
|
+
}
|
|
472
|
+
onRender(callback) {
|
|
473
|
+
this.renderCallbacks.add(callback);
|
|
474
|
+
return () => {
|
|
475
|
+
this.renderCallbacks.delete(callback);
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
notifyRendered() {
|
|
479
|
+
this.renderCallbacks.forEach((callback) => {
|
|
480
|
+
callback();
|
|
481
|
+
});
|
|
482
|
+
}
|
|
404
483
|
/**
|
|
405
484
|
* Get layout-aware components in order
|
|
406
485
|
* Override in subclasses to provide chart-specific components
|
|
@@ -424,7 +503,7 @@ export class BaseChart {
|
|
|
424
503
|
if (options.yAxis && this.yAxis) {
|
|
425
504
|
components.push(this.yAxis);
|
|
426
505
|
}
|
|
427
|
-
if (options.inlineLegend && this.legend
|
|
506
|
+
if (options.inlineLegend && this.legend && this.hasInlineLegend()) {
|
|
428
507
|
components.push(this.legend);
|
|
429
508
|
}
|
|
430
509
|
return components;
|
|
@@ -444,24 +523,12 @@ export class BaseChart {
|
|
|
444
523
|
}
|
|
445
524
|
getBaseExportComponents(options) {
|
|
446
525
|
const components = [];
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if (options.xAxis && this.xAxis) {
|
|
454
|
-
components.push(this.xAxis);
|
|
455
|
-
}
|
|
456
|
-
if (options.yAxis && this.yAxis) {
|
|
457
|
-
components.push(this.yAxis);
|
|
458
|
-
}
|
|
459
|
-
if (options.tooltip && this.tooltip) {
|
|
460
|
-
components.push(this.tooltip);
|
|
461
|
-
}
|
|
462
|
-
if (options.legend && this.legend) {
|
|
463
|
-
components.push(this.legend);
|
|
464
|
-
}
|
|
526
|
+
this.pushIfIncluded(components, options.title, this.title);
|
|
527
|
+
this.pushIfIncluded(components, options.grid, this.grid);
|
|
528
|
+
this.pushIfIncluded(components, options.xAxis, this.xAxis);
|
|
529
|
+
this.pushIfIncluded(components, options.yAxis, this.yAxis);
|
|
530
|
+
this.pushIfIncluded(components, options.tooltip, this.tooltip);
|
|
531
|
+
this.pushIfIncluded(components, options.legend && this.shouldIncludeLegendInExport(), this.legend);
|
|
465
532
|
return components;
|
|
466
533
|
}
|
|
467
534
|
registerBaseComponent(component) {
|
|
@@ -630,25 +697,25 @@ export class BaseChart {
|
|
|
630
697
|
this.title.render(svg, this.renderTheme, this.width, position.x, position.y);
|
|
631
698
|
}
|
|
632
699
|
renderInlineLegend(svg) {
|
|
633
|
-
if (!this.legend
|
|
700
|
+
if (!this.legend || !this.hasInlineLegend()) {
|
|
634
701
|
return;
|
|
635
702
|
}
|
|
703
|
+
const series = this.getLegendSeries();
|
|
704
|
+
this.legendState.ensureSeries(series.map((entry) => entry.dataKey), { silent: true });
|
|
636
705
|
const position = this.layoutManager.getComponentPosition(this.legend);
|
|
637
|
-
this.legend.render(svg,
|
|
706
|
+
this.legend.render(svg, series, this.renderTheme, this.width, position.x, position.y);
|
|
638
707
|
}
|
|
639
708
|
measureInlineLegend(svgNode) {
|
|
640
|
-
if (!this.legend
|
|
709
|
+
if (!this.legend || !this.hasInlineLegend()) {
|
|
641
710
|
return;
|
|
642
711
|
}
|
|
643
|
-
this.
|
|
712
|
+
const series = this.getLegendSeries();
|
|
713
|
+
this.legendState.ensureSeries(series.map((entry) => entry.dataKey), { silent: true });
|
|
714
|
+
this.legend.estimateLayoutSpace(series, this.renderTheme, this.width, svgNode);
|
|
644
715
|
}
|
|
645
716
|
filterVisibleItems(items, getDataKey) {
|
|
646
|
-
const { legend } = this;
|
|
647
|
-
if (!legend) {
|
|
648
|
-
return items;
|
|
649
|
-
}
|
|
650
717
|
return items.filter((item) => {
|
|
651
|
-
return
|
|
718
|
+
return this.legendState.isSeriesVisible(getDataKey(item));
|
|
652
719
|
});
|
|
653
720
|
}
|
|
654
721
|
validateSourceData(_data) { }
|
|
@@ -683,40 +750,31 @@ export class BaseChart {
|
|
|
683
750
|
return [];
|
|
684
751
|
}
|
|
685
752
|
getLegendItems() {
|
|
686
|
-
if (!this.legend) {
|
|
687
|
-
return [];
|
|
688
|
-
}
|
|
689
753
|
return this.getLegendSeries().map((series) => {
|
|
690
754
|
return {
|
|
691
755
|
dataKey: series.dataKey,
|
|
692
756
|
color: series.stroke || series.fill || '#8884d8',
|
|
693
|
-
visible: this.
|
|
757
|
+
visible: this.legendState.isSeriesVisible(series.dataKey),
|
|
694
758
|
};
|
|
695
759
|
});
|
|
696
760
|
}
|
|
697
761
|
isLegendSeriesVisible(dataKey) {
|
|
698
|
-
|
|
699
|
-
return true;
|
|
700
|
-
}
|
|
701
|
-
return this.legend.isSeriesVisible(dataKey);
|
|
762
|
+
return this.legendState.isSeriesVisible(dataKey);
|
|
702
763
|
}
|
|
703
764
|
setLegendSeriesVisible(dataKey, visible) {
|
|
704
|
-
this.
|
|
765
|
+
this.legendState.setSeriesVisible(dataKey, visible);
|
|
705
766
|
return this;
|
|
706
767
|
}
|
|
707
768
|
toggleLegendSeries(dataKey) {
|
|
708
|
-
this.
|
|
769
|
+
this.legendState.toggleSeries(dataKey);
|
|
709
770
|
return this;
|
|
710
771
|
}
|
|
711
772
|
setLegendVisibility(visibility) {
|
|
712
|
-
this.
|
|
773
|
+
this.legendState.setVisibilityMap(visibility);
|
|
713
774
|
return this;
|
|
714
775
|
}
|
|
715
776
|
onLegendChange(callback) {
|
|
716
|
-
|
|
717
|
-
return () => { };
|
|
718
|
-
}
|
|
719
|
-
return this.legend.subscribe(callback);
|
|
777
|
+
return this.legendState.subscribe(callback);
|
|
720
778
|
}
|
|
721
779
|
/**
|
|
722
780
|
* Updates the chart with new data
|
|
@@ -758,7 +816,7 @@ export class BaseChart {
|
|
|
758
816
|
this.cleanupDisconnectedLegendContainer();
|
|
759
817
|
return;
|
|
760
818
|
}
|
|
761
|
-
if (!this.
|
|
819
|
+
if (!this.hasDisconnectedLegend()) {
|
|
762
820
|
this.cleanupDisconnectedLegendContainer();
|
|
763
821
|
return;
|
|
764
822
|
}
|
|
@@ -767,6 +825,7 @@ export class BaseChart {
|
|
|
767
825
|
this.cleanupDisconnectedLegendContainer();
|
|
768
826
|
return;
|
|
769
827
|
}
|
|
828
|
+
this.legendState.ensureSeries(series.map((entry) => entry.dataKey), { silent: true });
|
|
770
829
|
const legendHost = this.resolveDisconnectedLegendHost();
|
|
771
830
|
if (!legendHost) {
|
|
772
831
|
this.cleanupDisconnectedLegendContainer();
|
|
@@ -927,9 +986,7 @@ export class BaseChart {
|
|
|
927
986
|
this.legend = component;
|
|
928
987
|
},
|
|
929
988
|
onRegister: (component) => {
|
|
930
|
-
component.
|
|
931
|
-
this.rerender();
|
|
932
|
-
});
|
|
989
|
+
component.setStateController(this.legendState);
|
|
933
990
|
},
|
|
934
991
|
},
|
|
935
992
|
];
|
|
@@ -1051,27 +1108,10 @@ export class BaseChart {
|
|
|
1051
1108
|
});
|
|
1052
1109
|
}
|
|
1053
1110
|
async exportSVG(options, formatForHooks = 'svg') {
|
|
1054
|
-
|
|
1055
|
-
throw new Error('Chart must be rendered before export');
|
|
1056
|
-
}
|
|
1111
|
+
const liveSvg = this.requireRenderedSvg();
|
|
1057
1112
|
await this.whenReady();
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
throw new Error('Chart must remain mounted until export completes');
|
|
1061
|
-
}
|
|
1062
|
-
const exportWidth = options?.width ?? this.width;
|
|
1063
|
-
const exportHeight = options?.height ?? this.height;
|
|
1064
|
-
const requiresExportRender = exportWidth !== this.width || exportHeight !== this.height;
|
|
1065
|
-
const clone = liveSvg.cloneNode(true);
|
|
1066
|
-
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
1067
|
-
clone.setAttribute('width', String(exportWidth));
|
|
1068
|
-
clone.setAttribute('height', String(exportHeight));
|
|
1069
|
-
const baseContext = {
|
|
1070
|
-
format: formatForHooks,
|
|
1071
|
-
options,
|
|
1072
|
-
width: exportWidth,
|
|
1073
|
-
height: exportHeight,
|
|
1074
|
-
};
|
|
1113
|
+
const { exportWidth, exportHeight, requiresExportRender, baseContext } = this.resolveExportContext(options, formatForHooks);
|
|
1114
|
+
const clone = this.createExportSvgClone(liveSvg, exportWidth, exportHeight);
|
|
1075
1115
|
const overrides = this.collectExportOverrides(baseContext);
|
|
1076
1116
|
if (overrides.size === 0 && !requiresExportRender) {
|
|
1077
1117
|
this.runExportHooks({
|
|
@@ -1086,17 +1126,7 @@ export class BaseChart {
|
|
|
1086
1126
|
width: exportWidth,
|
|
1087
1127
|
height: exportHeight,
|
|
1088
1128
|
};
|
|
1089
|
-
|
|
1090
|
-
components.forEach((component) => {
|
|
1091
|
-
const exportable = component;
|
|
1092
|
-
const override = overrides.get(component);
|
|
1093
|
-
if (exportable.createExportComponent) {
|
|
1094
|
-
exportChart.addChild(exportable.createExportComponent(override));
|
|
1095
|
-
}
|
|
1096
|
-
else {
|
|
1097
|
-
exportChart.addChild(component);
|
|
1098
|
-
}
|
|
1099
|
-
});
|
|
1129
|
+
this.populateExportChart(exportChart, overrides);
|
|
1100
1130
|
const exportSvg = await this.renderExportChart(exportChart, exportWidth, exportHeight);
|
|
1101
1131
|
exportSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
1102
1132
|
exportSvg.setAttribute('width', String(exportWidth));
|
|
@@ -1108,6 +1138,49 @@ export class BaseChart {
|
|
|
1108
1138
|
this.syncAccessibleLabelFromSvg(exportSvg);
|
|
1109
1139
|
return exportSvg.outerHTML;
|
|
1110
1140
|
}
|
|
1141
|
+
requireRenderedSvg() {
|
|
1142
|
+
if (!this.svg) {
|
|
1143
|
+
throw new Error('Chart must be rendered before export');
|
|
1144
|
+
}
|
|
1145
|
+
const liveSvg = this.svg.node();
|
|
1146
|
+
if (!liveSvg) {
|
|
1147
|
+
throw new Error('Chart must remain mounted until export completes');
|
|
1148
|
+
}
|
|
1149
|
+
return liveSvg;
|
|
1150
|
+
}
|
|
1151
|
+
resolveExportContext(options, format) {
|
|
1152
|
+
const exportWidth = options?.width ?? this.width;
|
|
1153
|
+
const exportHeight = options?.height ?? this.height;
|
|
1154
|
+
return {
|
|
1155
|
+
exportWidth,
|
|
1156
|
+
exportHeight,
|
|
1157
|
+
requiresExportRender: exportWidth !== this.width || exportHeight !== this.height,
|
|
1158
|
+
baseContext: {
|
|
1159
|
+
format,
|
|
1160
|
+
options,
|
|
1161
|
+
width: exportWidth,
|
|
1162
|
+
height: exportHeight,
|
|
1163
|
+
},
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
createExportSvgClone(liveSvg, width, height) {
|
|
1167
|
+
const clone = liveSvg.cloneNode(true);
|
|
1168
|
+
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
1169
|
+
clone.setAttribute('width', String(width));
|
|
1170
|
+
clone.setAttribute('height', String(height));
|
|
1171
|
+
return clone;
|
|
1172
|
+
}
|
|
1173
|
+
populateExportChart(exportChart, overrides) {
|
|
1174
|
+
this.getExportComponents().forEach((component) => {
|
|
1175
|
+
const exportable = component;
|
|
1176
|
+
const override = overrides.get(component);
|
|
1177
|
+
if (exportable.createExportComponent) {
|
|
1178
|
+
exportChart.addChild(exportable.createExportComponent(override));
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
exportChart.addChild(component);
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1111
1184
|
exportJSON() {
|
|
1112
1185
|
return JSON.stringify(this.sourceData, null, 2);
|
|
1113
1186
|
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { BaseChart } from './base-chart.js';
|
|
2
|
+
import type { ChartTheme, DeepPartial, ExportOptions, LegendItem } from './types.js';
|
|
3
|
+
type BreakpointRange = {
|
|
4
|
+
minWidth?: number;
|
|
5
|
+
maxWidth?: number;
|
|
6
|
+
};
|
|
7
|
+
export type ChartGroupResponsiveBreakpointConfig = BreakpointRange & {
|
|
8
|
+
cols?: number;
|
|
9
|
+
gap?: number;
|
|
10
|
+
};
|
|
11
|
+
export type ChartGroupResponsiveBreakpointDefinition = number | ChartGroupResponsiveBreakpointConfig;
|
|
12
|
+
export type ChartGroupResponsiveBreakpoints = Record<string, ChartGroupResponsiveBreakpointDefinition>;
|
|
13
|
+
export type ChartGroupResponsiveConfig = {
|
|
14
|
+
breakpoints?: ChartGroupResponsiveBreakpoints;
|
|
15
|
+
};
|
|
16
|
+
export type ChartGroupItemResponsiveBreakpointConfig = BreakpointRange & {
|
|
17
|
+
span?: number;
|
|
18
|
+
height?: number;
|
|
19
|
+
hidden?: boolean;
|
|
20
|
+
order?: number;
|
|
21
|
+
};
|
|
22
|
+
export type ChartGroupItemResponsiveBreakpointDefinition = number | ChartGroupItemResponsiveBreakpointConfig;
|
|
23
|
+
export type ChartGroupItemResponsiveBreakpoints = Record<string, ChartGroupItemResponsiveBreakpointDefinition>;
|
|
24
|
+
export type ChartGroupItemResponsiveConfig = {
|
|
25
|
+
breakpoints?: ChartGroupItemResponsiveBreakpoints;
|
|
26
|
+
};
|
|
27
|
+
export type ChartGroupConfig = {
|
|
28
|
+
cols: number;
|
|
29
|
+
gap?: number;
|
|
30
|
+
syncY?: boolean;
|
|
31
|
+
height?: number;
|
|
32
|
+
chartHeight?: number;
|
|
33
|
+
theme?: DeepPartial<ChartTheme>;
|
|
34
|
+
responsive?: ChartGroupResponsiveConfig;
|
|
35
|
+
};
|
|
36
|
+
export type ChartGroupChartOptions = {
|
|
37
|
+
span?: number;
|
|
38
|
+
height?: number;
|
|
39
|
+
hidden?: boolean;
|
|
40
|
+
order?: number;
|
|
41
|
+
responsive?: ChartGroupItemResponsiveConfig;
|
|
42
|
+
};
|
|
43
|
+
export type ChartGroupExportFormat = 'svg' | 'png' | 'jpg' | 'pdf';
|
|
44
|
+
export declare class ChartGroup {
|
|
45
|
+
private readonly cols;
|
|
46
|
+
private readonly gap;
|
|
47
|
+
private readonly syncY;
|
|
48
|
+
private readonly configuredHeight?;
|
|
49
|
+
private readonly chartHeight;
|
|
50
|
+
private readonly theme;
|
|
51
|
+
private readonly responsiveConfig?;
|
|
52
|
+
private readonly charts;
|
|
53
|
+
private readonly legendState;
|
|
54
|
+
private readonly renderCallbacks;
|
|
55
|
+
private readonly warnedWidthCharts;
|
|
56
|
+
private readonly warnedColorConflicts;
|
|
57
|
+
private container;
|
|
58
|
+
private legend;
|
|
59
|
+
private title;
|
|
60
|
+
private resizeObserver;
|
|
61
|
+
private readyPromise;
|
|
62
|
+
private childLegendSnapshot;
|
|
63
|
+
private childYDomainSnapshot;
|
|
64
|
+
private isRendering;
|
|
65
|
+
private isSyncingLegend;
|
|
66
|
+
private hasWarnedIncompatibleYScaleTypes;
|
|
67
|
+
constructor(config: ChartGroupConfig);
|
|
68
|
+
addChart(chart: BaseChart, options?: ChartGroupChartOptions): this;
|
|
69
|
+
addChild(component: {
|
|
70
|
+
type: string;
|
|
71
|
+
}): this;
|
|
72
|
+
render(target: string | HTMLElement): HTMLElement | null;
|
|
73
|
+
refresh(): HTMLElement | null;
|
|
74
|
+
whenReady(): Promise<void>;
|
|
75
|
+
getLegendItems(): LegendItem[];
|
|
76
|
+
private getLegendItemsForWidth;
|
|
77
|
+
isLegendSeriesVisible(dataKey: string): boolean;
|
|
78
|
+
setLegendSeriesVisible(dataKey: string, visible: boolean): this;
|
|
79
|
+
toggleLegendSeries(dataKey: string): this;
|
|
80
|
+
setLegendVisibility(visibility: Record<string, boolean>): this;
|
|
81
|
+
onLegendChange(callback: () => void): () => void;
|
|
82
|
+
export(format: ChartGroupExportFormat, options?: ExportOptions): Promise<string | Blob | void>;
|
|
83
|
+
destroy(): void;
|
|
84
|
+
private bindChartRenderCallback;
|
|
85
|
+
private resolveContainer;
|
|
86
|
+
private resolveContainerWidth;
|
|
87
|
+
private resolveCurrentWidth;
|
|
88
|
+
private resolveTotalHeightConstraint;
|
|
89
|
+
private resolveChartAreaHeightConstraint;
|
|
90
|
+
private resolveSpan;
|
|
91
|
+
private warnOnExplicitChildWidth;
|
|
92
|
+
private getAllVerticalXYCharts;
|
|
93
|
+
private getVerticalXYCharts;
|
|
94
|
+
private resolveSharedYDomain;
|
|
95
|
+
private warnIncompatibleYScaleTypes;
|
|
96
|
+
private applyScaleSyncOverrides;
|
|
97
|
+
private buildRows;
|
|
98
|
+
private resolveDefaultChartHeight;
|
|
99
|
+
private resolveDefaultChartHeightForWidth;
|
|
100
|
+
private calculateLayout;
|
|
101
|
+
private collectMergedLegendEntries;
|
|
102
|
+
private resolveLegendVisibility;
|
|
103
|
+
private syncLegendStateFromChildren;
|
|
104
|
+
private applyLegendStateToChildren;
|
|
105
|
+
private serializeChildLegendState;
|
|
106
|
+
private serializeChildYDomains;
|
|
107
|
+
private renderLegendIntoContainer;
|
|
108
|
+
private renderTitleSvg;
|
|
109
|
+
private renderLegendSvg;
|
|
110
|
+
private setupResizeObserver;
|
|
111
|
+
private exportSVG;
|
|
112
|
+
private resolveChartOptions;
|
|
113
|
+
private refreshIfMounted;
|
|
114
|
+
private prepareRenderState;
|
|
115
|
+
private createRenderHosts;
|
|
116
|
+
private appendRenderedSection;
|
|
117
|
+
private renderLayoutItems;
|
|
118
|
+
private createChartHost;
|
|
119
|
+
private createReadyPromise;
|
|
120
|
+
private requireRenderedContainer;
|
|
121
|
+
private resolveExportContent;
|
|
122
|
+
private createRasterExportOptions;
|
|
123
|
+
private exportPdfContent;
|
|
124
|
+
private resolveExportLayoutState;
|
|
125
|
+
private resolveLiveChartHeightOverride;
|
|
126
|
+
private exportLayoutItems;
|
|
127
|
+
private composeExportSvg;
|
|
128
|
+
private validateResponsiveConfig;
|
|
129
|
+
private validateItemResponsiveConfig;
|
|
130
|
+
private resolveResponsiveConfigForWidth;
|
|
131
|
+
private resolveMatchedGroupBreakpoints;
|
|
132
|
+
private resolveVisibleChartEntries;
|
|
133
|
+
private resolveResponsiveChartEntry;
|
|
134
|
+
private downloadContent;
|
|
135
|
+
private getMimeType;
|
|
136
|
+
}
|
|
137
|
+
export {};
|