@internetstiftelsen/charts 0.9.2 → 0.10.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 +136 -2
- package/dist/bar.d.ts +3 -1
- package/dist/bar.js +167 -327
- package/dist/base-chart.d.ts +16 -1
- package/dist/base-chart.js +89 -30
- package/dist/chart-group.d.ts +121 -0
- package/dist/chart-group.js +1097 -0
- package/dist/chart-interface.d.ts +1 -1
- package/dist/donut-chart.js +1 -1
- package/dist/gauge-chart.js +1 -1
- 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 +35 -29
- package/dist/pie-chart.js +1 -1
- package/dist/scatter.d.ts +16 -0
- package/dist/scatter.js +163 -0
- package/dist/tooltip.d.ts +2 -1
- package/dist/tooltip.js +3 -3
- package/dist/types.d.ts +16 -0
- package/dist/validation.d.ts +4 -0
- package/dist/validation.js +19 -0
- package/dist/xy-chart.d.ts +16 -1
- package/dist/xy-chart.js +317 -102
- package/docs/chart-group.md +213 -0
- package/docs/components.md +308 -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 +502 -0
- package/package.json +2 -1
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
|
|
@@ -112,6 +116,17 @@ export declare abstract class BaseChart {
|
|
|
112
116
|
private applyRenderTheme;
|
|
113
117
|
protected get renderTheme(): ChartTheme;
|
|
114
118
|
protected get resolvedRenderTheme(): ResolvedChartTheme;
|
|
119
|
+
protected getLegendMode(): LegendMode | null;
|
|
120
|
+
protected hasInlineLegend(): boolean;
|
|
121
|
+
protected hasDisconnectedLegend(): boolean;
|
|
122
|
+
protected shouldIncludeLegendInExport(): boolean;
|
|
123
|
+
getConfiguredSize(): {
|
|
124
|
+
width?: number;
|
|
125
|
+
height?: number;
|
|
126
|
+
};
|
|
127
|
+
setLegendModeOverride(mode: LegendMode | null, rerender?: boolean): this;
|
|
128
|
+
onRender(callback: () => void): () => void;
|
|
129
|
+
private notifyRendered;
|
|
115
130
|
/**
|
|
116
131
|
* Get layout-aware components in order
|
|
117
132
|
* Override in subclasses to provide chart-specific components
|
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();
|
|
@@ -401,6 +425,49 @@ export class BaseChart {
|
|
|
401
425
|
height: this.height,
|
|
402
426
|
};
|
|
403
427
|
}
|
|
428
|
+
getLegendMode() {
|
|
429
|
+
if (!this.legend) {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
return this.legendModeOverride ?? this.legend.mode;
|
|
433
|
+
}
|
|
434
|
+
hasInlineLegend() {
|
|
435
|
+
return this.getLegendMode() === 'inline';
|
|
436
|
+
}
|
|
437
|
+
hasDisconnectedLegend() {
|
|
438
|
+
return this.getLegendMode() === 'disconnected';
|
|
439
|
+
}
|
|
440
|
+
shouldIncludeLegendInExport() {
|
|
441
|
+
const legendMode = this.getLegendMode();
|
|
442
|
+
return !!this.legend && legendMode !== null && legendMode !== 'hidden';
|
|
443
|
+
}
|
|
444
|
+
getConfiguredSize() {
|
|
445
|
+
return {
|
|
446
|
+
width: this.configuredWidth,
|
|
447
|
+
height: this.configuredHeight,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
setLegendModeOverride(mode, rerender = true) {
|
|
451
|
+
if (this.legendModeOverride === mode) {
|
|
452
|
+
return this;
|
|
453
|
+
}
|
|
454
|
+
this.legendModeOverride = mode;
|
|
455
|
+
if (rerender) {
|
|
456
|
+
this.rerender();
|
|
457
|
+
}
|
|
458
|
+
return this;
|
|
459
|
+
}
|
|
460
|
+
onRender(callback) {
|
|
461
|
+
this.renderCallbacks.add(callback);
|
|
462
|
+
return () => {
|
|
463
|
+
this.renderCallbacks.delete(callback);
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
notifyRendered() {
|
|
467
|
+
this.renderCallbacks.forEach((callback) => {
|
|
468
|
+
callback();
|
|
469
|
+
});
|
|
470
|
+
}
|
|
404
471
|
/**
|
|
405
472
|
* Get layout-aware components in order
|
|
406
473
|
* Override in subclasses to provide chart-specific components
|
|
@@ -424,7 +491,7 @@ export class BaseChart {
|
|
|
424
491
|
if (options.yAxis && this.yAxis) {
|
|
425
492
|
components.push(this.yAxis);
|
|
426
493
|
}
|
|
427
|
-
if (options.inlineLegend && this.legend
|
|
494
|
+
if (options.inlineLegend && this.legend && this.hasInlineLegend()) {
|
|
428
495
|
components.push(this.legend);
|
|
429
496
|
}
|
|
430
497
|
return components;
|
|
@@ -459,7 +526,9 @@ export class BaseChart {
|
|
|
459
526
|
if (options.tooltip && this.tooltip) {
|
|
460
527
|
components.push(this.tooltip);
|
|
461
528
|
}
|
|
462
|
-
if (options.legend &&
|
|
529
|
+
if (options.legend &&
|
|
530
|
+
this.legend &&
|
|
531
|
+
this.shouldIncludeLegendInExport()) {
|
|
463
532
|
components.push(this.legend);
|
|
464
533
|
}
|
|
465
534
|
return components;
|
|
@@ -630,25 +699,25 @@ export class BaseChart {
|
|
|
630
699
|
this.title.render(svg, this.renderTheme, this.width, position.x, position.y);
|
|
631
700
|
}
|
|
632
701
|
renderInlineLegend(svg) {
|
|
633
|
-
if (!this.legend
|
|
702
|
+
if (!this.legend || !this.hasInlineLegend()) {
|
|
634
703
|
return;
|
|
635
704
|
}
|
|
705
|
+
const series = this.getLegendSeries();
|
|
706
|
+
this.legendState.ensureSeries(series.map((entry) => entry.dataKey), { silent: true });
|
|
636
707
|
const position = this.layoutManager.getComponentPosition(this.legend);
|
|
637
|
-
this.legend.render(svg,
|
|
708
|
+
this.legend.render(svg, series, this.renderTheme, this.width, position.x, position.y);
|
|
638
709
|
}
|
|
639
710
|
measureInlineLegend(svgNode) {
|
|
640
|
-
if (!this.legend
|
|
711
|
+
if (!this.legend || !this.hasInlineLegend()) {
|
|
641
712
|
return;
|
|
642
713
|
}
|
|
643
|
-
this.
|
|
714
|
+
const series = this.getLegendSeries();
|
|
715
|
+
this.legendState.ensureSeries(series.map((entry) => entry.dataKey), { silent: true });
|
|
716
|
+
this.legend.estimateLayoutSpace(series, this.renderTheme, this.width, svgNode);
|
|
644
717
|
}
|
|
645
718
|
filterVisibleItems(items, getDataKey) {
|
|
646
|
-
const { legend } = this;
|
|
647
|
-
if (!legend) {
|
|
648
|
-
return items;
|
|
649
|
-
}
|
|
650
719
|
return items.filter((item) => {
|
|
651
|
-
return
|
|
720
|
+
return this.legendState.isSeriesVisible(getDataKey(item));
|
|
652
721
|
});
|
|
653
722
|
}
|
|
654
723
|
validateSourceData(_data) { }
|
|
@@ -683,40 +752,31 @@ export class BaseChart {
|
|
|
683
752
|
return [];
|
|
684
753
|
}
|
|
685
754
|
getLegendItems() {
|
|
686
|
-
if (!this.legend) {
|
|
687
|
-
return [];
|
|
688
|
-
}
|
|
689
755
|
return this.getLegendSeries().map((series) => {
|
|
690
756
|
return {
|
|
691
757
|
dataKey: series.dataKey,
|
|
692
758
|
color: series.stroke || series.fill || '#8884d8',
|
|
693
|
-
visible: this.
|
|
759
|
+
visible: this.legendState.isSeriesVisible(series.dataKey),
|
|
694
760
|
};
|
|
695
761
|
});
|
|
696
762
|
}
|
|
697
763
|
isLegendSeriesVisible(dataKey) {
|
|
698
|
-
|
|
699
|
-
return true;
|
|
700
|
-
}
|
|
701
|
-
return this.legend.isSeriesVisible(dataKey);
|
|
764
|
+
return this.legendState.isSeriesVisible(dataKey);
|
|
702
765
|
}
|
|
703
766
|
setLegendSeriesVisible(dataKey, visible) {
|
|
704
|
-
this.
|
|
767
|
+
this.legendState.setSeriesVisible(dataKey, visible);
|
|
705
768
|
return this;
|
|
706
769
|
}
|
|
707
770
|
toggleLegendSeries(dataKey) {
|
|
708
|
-
this.
|
|
771
|
+
this.legendState.toggleSeries(dataKey);
|
|
709
772
|
return this;
|
|
710
773
|
}
|
|
711
774
|
setLegendVisibility(visibility) {
|
|
712
|
-
this.
|
|
775
|
+
this.legendState.setVisibilityMap(visibility);
|
|
713
776
|
return this;
|
|
714
777
|
}
|
|
715
778
|
onLegendChange(callback) {
|
|
716
|
-
|
|
717
|
-
return () => { };
|
|
718
|
-
}
|
|
719
|
-
return this.legend.subscribe(callback);
|
|
779
|
+
return this.legendState.subscribe(callback);
|
|
720
780
|
}
|
|
721
781
|
/**
|
|
722
782
|
* Updates the chart with new data
|
|
@@ -758,7 +818,7 @@ export class BaseChart {
|
|
|
758
818
|
this.cleanupDisconnectedLegendContainer();
|
|
759
819
|
return;
|
|
760
820
|
}
|
|
761
|
-
if (!this.
|
|
821
|
+
if (!this.hasDisconnectedLegend()) {
|
|
762
822
|
this.cleanupDisconnectedLegendContainer();
|
|
763
823
|
return;
|
|
764
824
|
}
|
|
@@ -767,6 +827,7 @@ export class BaseChart {
|
|
|
767
827
|
this.cleanupDisconnectedLegendContainer();
|
|
768
828
|
return;
|
|
769
829
|
}
|
|
830
|
+
this.legendState.ensureSeries(series.map((entry) => entry.dataKey), { silent: true });
|
|
770
831
|
const legendHost = this.resolveDisconnectedLegendHost();
|
|
771
832
|
if (!legendHost) {
|
|
772
833
|
this.cleanupDisconnectedLegendContainer();
|
|
@@ -927,9 +988,7 @@ export class BaseChart {
|
|
|
927
988
|
this.legend = component;
|
|
928
989
|
},
|
|
929
990
|
onRegister: (component) => {
|
|
930
|
-
component.
|
|
931
|
-
this.rerender();
|
|
932
|
-
});
|
|
991
|
+
component.setStateController(this.legendState);
|
|
933
992
|
},
|
|
934
993
|
},
|
|
935
994
|
];
|
|
@@ -0,0 +1,121 @@
|
|
|
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 validateResponsiveConfig;
|
|
113
|
+
private validateItemResponsiveConfig;
|
|
114
|
+
private resolveResponsiveConfigForWidth;
|
|
115
|
+
private resolveMatchedGroupBreakpoints;
|
|
116
|
+
private resolveVisibleChartEntries;
|
|
117
|
+
private resolveResponsiveChartEntry;
|
|
118
|
+
private downloadContent;
|
|
119
|
+
private getMimeType;
|
|
120
|
+
}
|
|
121
|
+
export {};
|