@internetstiftelsen/charts 0.3.2 → 0.4.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/bar.d.ts +5 -2
- package/bar.js +25 -1
- package/base-chart.d.ts +8 -2
- package/base-chart.js +121 -9
- package/chart-interface.d.ts +4 -2
- package/donut-center-content.d.ts +9 -3
- package/donut-center-content.js +25 -0
- package/donut-chart.d.ts +2 -0
- package/donut-chart.js +33 -2
- package/grid.d.ts +5 -2
- package/grid.js +21 -0
- package/legend.d.ts +5 -2
- package/legend.js +24 -0
- package/line.d.ts +5 -2
- package/line.js +23 -1
- package/package.json +1 -1
- package/title.d.ts +5 -2
- package/title.js +26 -0
- package/tooltip.d.ts +5 -2
- package/tooltip.js +22 -1
- package/types.d.ts +47 -8
- package/utils.d.ts +1 -0
- package/utils.js +22 -0
- package/x-axis.d.ts +9 -3
- package/x-axis.js +87 -1
- package/xy-chart.d.ts +3 -0
- package/xy-chart.js +42 -0
- package/y-axis.d.ts +5 -2
- package/y-axis.js +23 -1
package/bar.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Selection } from 'd3';
|
|
2
|
-
import type { BarConfig, BarStackingContext, BarValueLabelConfig, ChartTheme, D3Scale, DataItem, Orientation, ScaleType } from './types.js';
|
|
2
|
+
import type { BarConfig, BarStackingContext, BarValueLabelConfig, ChartTheme, D3Scale, DataItem, Orientation, ScaleType, ExportHooks, BarConfigBase } from './types.js';
|
|
3
3
|
import type { ChartComponent } from './chart-interface.js';
|
|
4
|
-
export declare class Bar implements ChartComponent {
|
|
4
|
+
export declare class Bar implements ChartComponent<BarConfigBase> {
|
|
5
5
|
readonly type: "bar";
|
|
6
6
|
readonly dataKey: string;
|
|
7
7
|
readonly fill: string;
|
|
@@ -9,7 +9,10 @@ export declare class Bar implements ChartComponent {
|
|
|
9
9
|
readonly orientation: Orientation;
|
|
10
10
|
readonly maxBarSize?: number;
|
|
11
11
|
readonly valueLabel?: BarValueLabelConfig;
|
|
12
|
+
readonly exportHooks?: ExportHooks<BarConfigBase>;
|
|
12
13
|
constructor(config: BarConfig);
|
|
14
|
+
getExportConfig(): BarConfigBase;
|
|
15
|
+
createExportComponent(override?: Partial<BarConfigBase>): ChartComponent;
|
|
13
16
|
private getScaledPosition;
|
|
14
17
|
render(plotGroup: Selection<SVGGElement, undefined, null, undefined>, data: DataItem[], xKey: string, x: D3Scale, y: D3Scale, parseValue: (value: unknown) => number, xScaleType?: ScaleType, theme?: ChartTheme, stackingContext?: BarStackingContext): void;
|
|
15
18
|
private renderVertical;
|
package/bar.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sanitizeForCSS } from './utils.js';
|
|
1
|
+
import { sanitizeForCSS, mergeDeep } from './utils.js';
|
|
2
2
|
export class Bar {
|
|
3
3
|
constructor(config) {
|
|
4
4
|
Object.defineProperty(this, "type", {
|
|
@@ -43,12 +43,36 @@ export class Bar {
|
|
|
43
43
|
writable: true,
|
|
44
44
|
value: void 0
|
|
45
45
|
});
|
|
46
|
+
Object.defineProperty(this, "exportHooks", {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
writable: true,
|
|
50
|
+
value: void 0
|
|
51
|
+
});
|
|
46
52
|
this.dataKey = config.dataKey;
|
|
47
53
|
this.fill = config.fill || '#8884d8';
|
|
48
54
|
this.colorAdapter = config.colorAdapter;
|
|
49
55
|
this.orientation = config.orientation || 'vertical';
|
|
50
56
|
this.maxBarSize = config.maxBarSize;
|
|
51
57
|
this.valueLabel = config.valueLabel;
|
|
58
|
+
this.exportHooks = config.exportHooks;
|
|
59
|
+
}
|
|
60
|
+
getExportConfig() {
|
|
61
|
+
return {
|
|
62
|
+
dataKey: this.dataKey,
|
|
63
|
+
fill: this.fill,
|
|
64
|
+
colorAdapter: this.colorAdapter,
|
|
65
|
+
orientation: this.orientation,
|
|
66
|
+
maxBarSize: this.maxBarSize,
|
|
67
|
+
valueLabel: this.valueLabel,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
createExportComponent(override) {
|
|
71
|
+
const merged = mergeDeep(this.getExportConfig(), override);
|
|
72
|
+
return new Bar({
|
|
73
|
+
...merged,
|
|
74
|
+
exportHooks: this.exportHooks,
|
|
75
|
+
});
|
|
52
76
|
}
|
|
53
77
|
getScaledPosition(data, key, scale, scaleType) {
|
|
54
78
|
const value = data[key];
|
package/base-chart.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type { DataItem, ChartTheme, AxisScaleConfig, ExportFormat, ExportOptions, D3Scale } from './types.js';
|
|
2
|
+
import type { DataItem, ChartTheme, AxisScaleConfig, ExportFormat, ExportOptions, D3Scale, ExportHookContext, ExportRenderContext } from './types.js';
|
|
3
3
|
import type { ChartComponent, LayoutAwareComponent } from './chart-interface.js';
|
|
4
4
|
import type { XAxis } from './x-axis.js';
|
|
5
5
|
import type { YAxis } from './y-axis.js';
|
|
@@ -54,6 +54,11 @@ export declare abstract class BaseChart {
|
|
|
54
54
|
* Override in subclasses to provide chart-specific components
|
|
55
55
|
*/
|
|
56
56
|
protected getLayoutComponents(): LayoutAwareComponent[];
|
|
57
|
+
protected getExportComponents(): ChartComponent[];
|
|
58
|
+
protected collectExportOverrides(context: ExportRenderContext): Map<ChartComponent, Record<string, unknown>>;
|
|
59
|
+
protected runExportHooks(context: ExportHookContext): void;
|
|
60
|
+
private renderExportChart;
|
|
61
|
+
protected prepareLayout(): void;
|
|
57
62
|
/**
|
|
58
63
|
* Setup ResizeObserver for automatic resize handling
|
|
59
64
|
*/
|
|
@@ -62,6 +67,7 @@ export declare abstract class BaseChart {
|
|
|
62
67
|
* Subclasses must implement this method to define their rendering logic
|
|
63
68
|
*/
|
|
64
69
|
protected abstract renderChart(): void;
|
|
70
|
+
protected abstract createExportChart(): BaseChart;
|
|
65
71
|
/**
|
|
66
72
|
* Updates the chart with new data
|
|
67
73
|
*/
|
|
@@ -82,6 +88,6 @@ export declare abstract class BaseChart {
|
|
|
82
88
|
* Downloads the exported content as a file
|
|
83
89
|
*/
|
|
84
90
|
private downloadContent;
|
|
85
|
-
protected exportSVG(): string;
|
|
91
|
+
protected exportSVG(options?: ExportOptions): string;
|
|
86
92
|
protected exportJSON(): string;
|
|
87
93
|
}
|
package/base-chart.js
CHANGED
|
@@ -164,6 +164,7 @@ export class BaseChart {
|
|
|
164
164
|
.attr('height', this.height)
|
|
165
165
|
.style('display', 'block');
|
|
166
166
|
this.container.appendChild(this.svg.node());
|
|
167
|
+
this.prepareLayout();
|
|
167
168
|
// Calculate layout
|
|
168
169
|
const layoutTheme = {
|
|
169
170
|
...this.theme,
|
|
@@ -184,16 +185,90 @@ export class BaseChart {
|
|
|
184
185
|
*/
|
|
185
186
|
getLayoutComponents() {
|
|
186
187
|
const components = [];
|
|
187
|
-
if (this.title)
|
|
188
|
+
if (this.title) {
|
|
188
189
|
components.push(this.title);
|
|
189
|
-
|
|
190
|
+
}
|
|
191
|
+
if (this.xAxis) {
|
|
190
192
|
components.push(this.xAxis);
|
|
191
|
-
|
|
193
|
+
}
|
|
194
|
+
if (this.yAxis) {
|
|
192
195
|
components.push(this.yAxis);
|
|
193
|
-
|
|
196
|
+
}
|
|
197
|
+
if (this.legend) {
|
|
194
198
|
components.push(this.legend);
|
|
199
|
+
}
|
|
195
200
|
return components;
|
|
196
201
|
}
|
|
202
|
+
getExportComponents() {
|
|
203
|
+
const components = [];
|
|
204
|
+
if (this.title) {
|
|
205
|
+
components.push(this.title);
|
|
206
|
+
}
|
|
207
|
+
if (this.grid) {
|
|
208
|
+
components.push(this.grid);
|
|
209
|
+
}
|
|
210
|
+
if (this.xAxis) {
|
|
211
|
+
components.push(this.xAxis);
|
|
212
|
+
}
|
|
213
|
+
if (this.yAxis) {
|
|
214
|
+
components.push(this.yAxis);
|
|
215
|
+
}
|
|
216
|
+
if (this.tooltip) {
|
|
217
|
+
components.push(this.tooltip);
|
|
218
|
+
}
|
|
219
|
+
if (this.legend) {
|
|
220
|
+
components.push(this.legend);
|
|
221
|
+
}
|
|
222
|
+
return components;
|
|
223
|
+
}
|
|
224
|
+
collectExportOverrides(context) {
|
|
225
|
+
const overrides = new Map();
|
|
226
|
+
const components = this.getExportComponents();
|
|
227
|
+
components.forEach((component) => {
|
|
228
|
+
const exportable = component;
|
|
229
|
+
const currentConfig = exportable.getExportConfig?.() ?? {};
|
|
230
|
+
const result = component.exportHooks?.beforeRender?.call(component, context, currentConfig);
|
|
231
|
+
if (result &&
|
|
232
|
+
typeof result === 'object' &&
|
|
233
|
+
exportable.createExportComponent) {
|
|
234
|
+
overrides.set(component, result);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
return overrides;
|
|
238
|
+
}
|
|
239
|
+
runExportHooks(context) {
|
|
240
|
+
const components = this.getExportComponents();
|
|
241
|
+
components.forEach((component) => {
|
|
242
|
+
component.exportHooks?.before?.call(component, context);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
renderExportChart(chart, width, height) {
|
|
246
|
+
const container = document.createElement('div');
|
|
247
|
+
const containerId = `chart-export-${Math.random()
|
|
248
|
+
.toString(36)
|
|
249
|
+
.slice(2)}`;
|
|
250
|
+
container.id = containerId;
|
|
251
|
+
container.style.position = 'absolute';
|
|
252
|
+
container.style.left = '-99999px';
|
|
253
|
+
container.style.top = '0';
|
|
254
|
+
container.style.width = `${width}px`;
|
|
255
|
+
container.style.height = `${height}px`;
|
|
256
|
+
container.style.visibility = 'hidden';
|
|
257
|
+
document.body.appendChild(container);
|
|
258
|
+
chart.render(`#${containerId}`);
|
|
259
|
+
const svg = chart.svg?.node();
|
|
260
|
+
if (!svg) {
|
|
261
|
+
chart.destroy();
|
|
262
|
+
document.body.removeChild(container);
|
|
263
|
+
throw new Error('Failed to render export SVG');
|
|
264
|
+
}
|
|
265
|
+
chart.destroy();
|
|
266
|
+
document.body.removeChild(container);
|
|
267
|
+
return svg;
|
|
268
|
+
}
|
|
269
|
+
// Hook for subclasses to update component layout estimates before layout calc
|
|
270
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
271
|
+
prepareLayout() { }
|
|
197
272
|
/**
|
|
198
273
|
* Setup ResizeObserver for automatic resize handling
|
|
199
274
|
*/
|
|
@@ -251,7 +326,7 @@ export class BaseChart {
|
|
|
251
326
|
* @returns The exported content as a string if download is false/undefined, void if download is true
|
|
252
327
|
*/
|
|
253
328
|
export(format, options) {
|
|
254
|
-
const content = format === 'svg' ? this.exportSVG() : this.exportJSON();
|
|
329
|
+
const content = format === 'svg' ? this.exportSVG(options) : this.exportJSON();
|
|
255
330
|
if (options?.download) {
|
|
256
331
|
this.downloadContent(content, format, options);
|
|
257
332
|
return;
|
|
@@ -273,15 +348,52 @@ export class BaseChart {
|
|
|
273
348
|
document.body.removeChild(link);
|
|
274
349
|
URL.revokeObjectURL(url);
|
|
275
350
|
}
|
|
276
|
-
exportSVG() {
|
|
351
|
+
exportSVG(options) {
|
|
277
352
|
if (!this.svg) {
|
|
278
353
|
throw new Error('Chart must be rendered before export');
|
|
279
354
|
}
|
|
355
|
+
const exportWidth = options?.width ?? this.width;
|
|
356
|
+
const exportHeight = options?.height ?? this.height;
|
|
357
|
+
const requiresExportRender = exportWidth !== this.width || exportHeight !== this.height;
|
|
280
358
|
const clone = this.svg.node().cloneNode(true);
|
|
281
359
|
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
282
|
-
clone.setAttribute('width', String(
|
|
283
|
-
clone.setAttribute('height', String(
|
|
284
|
-
|
|
360
|
+
clone.setAttribute('width', String(exportWidth));
|
|
361
|
+
clone.setAttribute('height', String(exportHeight));
|
|
362
|
+
const baseContext = {
|
|
363
|
+
format: 'svg',
|
|
364
|
+
options,
|
|
365
|
+
width: exportWidth,
|
|
366
|
+
height: exportHeight,
|
|
367
|
+
};
|
|
368
|
+
const overrides = this.collectExportOverrides(baseContext);
|
|
369
|
+
if (overrides.size === 0 && !requiresExportRender) {
|
|
370
|
+
this.runExportHooks({
|
|
371
|
+
...baseContext,
|
|
372
|
+
svg: clone,
|
|
373
|
+
});
|
|
374
|
+
return clone.outerHTML;
|
|
375
|
+
}
|
|
376
|
+
const exportChart = this.createExportChart();
|
|
377
|
+
const components = this.getExportComponents();
|
|
378
|
+
components.forEach((component) => {
|
|
379
|
+
const exportable = component;
|
|
380
|
+
const override = overrides.get(component);
|
|
381
|
+
if (exportable.createExportComponent) {
|
|
382
|
+
exportChart.addChild(exportable.createExportComponent(override));
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
exportChart.addChild(component);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
const exportSvg = this.renderExportChart(exportChart, exportWidth, exportHeight);
|
|
389
|
+
exportSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
390
|
+
exportSvg.setAttribute('width', String(exportWidth));
|
|
391
|
+
exportSvg.setAttribute('height', String(exportHeight));
|
|
392
|
+
this.runExportHooks({
|
|
393
|
+
...baseContext,
|
|
394
|
+
svg: exportSvg,
|
|
395
|
+
});
|
|
396
|
+
return exportSvg.outerHTML;
|
|
285
397
|
}
|
|
286
398
|
exportJSON() {
|
|
287
399
|
return JSON.stringify({
|
package/chart-interface.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ExportHooks } from './types.js';
|
|
2
|
+
export interface ChartComponent<TConfig = any> {
|
|
2
3
|
type: 'line' | 'bar' | 'xAxis' | 'yAxis' | 'grid' | 'tooltip' | 'legend' | 'title' | 'donutCenterContent';
|
|
4
|
+
exportHooks?: ExportHooks<TConfig>;
|
|
3
5
|
}
|
|
4
6
|
export type ComponentSpace = {
|
|
5
7
|
width: number;
|
|
6
8
|
height: number;
|
|
7
9
|
position: 'top' | 'bottom' | 'left' | 'right';
|
|
8
10
|
};
|
|
9
|
-
export interface LayoutAwareComponent extends ChartComponent {
|
|
11
|
+
export interface LayoutAwareComponent<TConfig = any> extends ChartComponent<TConfig> {
|
|
10
12
|
getRequiredSpace(): ComponentSpace;
|
|
11
13
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type { ChartTheme } from './types.js';
|
|
2
|
+
import type { ChartTheme, ExportHooks } from './types.js';
|
|
3
3
|
import type { ChartComponent } from './chart-interface.js';
|
|
4
4
|
type TextStyle = {
|
|
5
5
|
fontSize?: number;
|
|
@@ -7,7 +7,7 @@ type TextStyle = {
|
|
|
7
7
|
fontFamily?: string;
|
|
8
8
|
color?: string;
|
|
9
9
|
};
|
|
10
|
-
export type
|
|
10
|
+
export type DonutCenterContentConfigBase = {
|
|
11
11
|
mainValue?: string;
|
|
12
12
|
title?: string;
|
|
13
13
|
subtitle?: string;
|
|
@@ -15,13 +15,19 @@ export type DonutCenterContentConfig = {
|
|
|
15
15
|
titleStyle?: TextStyle;
|
|
16
16
|
subtitleStyle?: TextStyle;
|
|
17
17
|
};
|
|
18
|
-
export
|
|
18
|
+
export type DonutCenterContentConfig = DonutCenterContentConfigBase & {
|
|
19
|
+
exportHooks?: ExportHooks<DonutCenterContentConfigBase>;
|
|
20
|
+
};
|
|
21
|
+
export declare class DonutCenterContent implements ChartComponent<DonutCenterContentConfigBase> {
|
|
19
22
|
readonly type: "donutCenterContent";
|
|
20
23
|
readonly mainValue?: string;
|
|
21
24
|
readonly title?: string;
|
|
22
25
|
readonly subtitle?: string;
|
|
26
|
+
readonly exportHooks?: ExportHooks<DonutCenterContentConfigBase>;
|
|
23
27
|
private readonly config;
|
|
24
28
|
constructor(config?: DonutCenterContentConfig);
|
|
29
|
+
getExportConfig(): DonutCenterContentConfigBase;
|
|
30
|
+
createExportComponent(override?: Partial<DonutCenterContentConfigBase>): ChartComponent;
|
|
25
31
|
render(svg: Selection<SVGSVGElement, undefined, null, undefined>, cx: number, cy: number, theme: ChartTheme): void;
|
|
26
32
|
}
|
|
27
33
|
export {};
|
package/donut-center-content.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mergeDeep } from './utils.js';
|
|
1
2
|
export class DonutCenterContent {
|
|
2
3
|
constructor(config = {}) {
|
|
3
4
|
Object.defineProperty(this, "type", {
|
|
@@ -24,6 +25,12 @@ export class DonutCenterContent {
|
|
|
24
25
|
writable: true,
|
|
25
26
|
value: void 0
|
|
26
27
|
});
|
|
28
|
+
Object.defineProperty(this, "exportHooks", {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
value: void 0
|
|
33
|
+
});
|
|
27
34
|
Object.defineProperty(this, "config", {
|
|
28
35
|
enumerable: true,
|
|
29
36
|
configurable: true,
|
|
@@ -33,8 +40,26 @@ export class DonutCenterContent {
|
|
|
33
40
|
this.mainValue = config.mainValue;
|
|
34
41
|
this.title = config.title;
|
|
35
42
|
this.subtitle = config.subtitle;
|
|
43
|
+
this.exportHooks = config.exportHooks;
|
|
36
44
|
this.config = config;
|
|
37
45
|
}
|
|
46
|
+
getExportConfig() {
|
|
47
|
+
return {
|
|
48
|
+
mainValue: this.mainValue,
|
|
49
|
+
title: this.title,
|
|
50
|
+
subtitle: this.subtitle,
|
|
51
|
+
mainValueStyle: this.config.mainValueStyle,
|
|
52
|
+
titleStyle: this.config.titleStyle,
|
|
53
|
+
subtitleStyle: this.config.subtitleStyle,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
createExportComponent(override) {
|
|
57
|
+
const merged = mergeDeep(this.getExportConfig(), override);
|
|
58
|
+
return new DonutCenterContent({
|
|
59
|
+
...merged,
|
|
60
|
+
exportHooks: this.exportHooks,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
38
63
|
render(svg, cx, cy, theme) {
|
|
39
64
|
const defaults = theme.donut.centerContent;
|
|
40
65
|
const elements = [];
|
package/donut-chart.d.ts
CHANGED
|
@@ -23,8 +23,10 @@ export declare class DonutChart extends BaseChart {
|
|
|
23
23
|
private validateDonutData;
|
|
24
24
|
private prepareSegments;
|
|
25
25
|
addChild(component: ChartComponent): this;
|
|
26
|
+
protected getExportComponents(): ChartComponent[];
|
|
26
27
|
update(data: DataItem[]): void;
|
|
27
28
|
protected getLayoutComponents(): LayoutAwareComponent[];
|
|
29
|
+
protected createExportChart(): BaseChart;
|
|
28
30
|
protected renderChart(): void;
|
|
29
31
|
private positionTooltip;
|
|
30
32
|
private buildTooltipContent;
|
package/donut-chart.js
CHANGED
|
@@ -97,6 +97,22 @@ export class DonutChart extends BaseChart {
|
|
|
97
97
|
}
|
|
98
98
|
return this;
|
|
99
99
|
}
|
|
100
|
+
getExportComponents() {
|
|
101
|
+
const components = [];
|
|
102
|
+
if (this.title) {
|
|
103
|
+
components.push(this.title);
|
|
104
|
+
}
|
|
105
|
+
if (this.centerContent) {
|
|
106
|
+
components.push(this.centerContent);
|
|
107
|
+
}
|
|
108
|
+
if (this.tooltip) {
|
|
109
|
+
components.push(this.tooltip);
|
|
110
|
+
}
|
|
111
|
+
if (this.legend) {
|
|
112
|
+
components.push(this.legend);
|
|
113
|
+
}
|
|
114
|
+
return components;
|
|
115
|
+
}
|
|
100
116
|
update(data) {
|
|
101
117
|
this.data = data;
|
|
102
118
|
this.validateDonutData();
|
|
@@ -105,12 +121,27 @@ export class DonutChart extends BaseChart {
|
|
|
105
121
|
}
|
|
106
122
|
getLayoutComponents() {
|
|
107
123
|
const components = [];
|
|
108
|
-
if (this.title)
|
|
124
|
+
if (this.title) {
|
|
109
125
|
components.push(this.title);
|
|
110
|
-
|
|
126
|
+
}
|
|
127
|
+
if (this.legend) {
|
|
111
128
|
components.push(this.legend);
|
|
129
|
+
}
|
|
112
130
|
return components;
|
|
113
131
|
}
|
|
132
|
+
createExportChart() {
|
|
133
|
+
return new DonutChart({
|
|
134
|
+
data: this.data,
|
|
135
|
+
theme: this.theme,
|
|
136
|
+
donut: {
|
|
137
|
+
innerRadius: this.innerRadiusRatio,
|
|
138
|
+
padAngle: this.padAngle,
|
|
139
|
+
cornerRadius: this.cornerRadius,
|
|
140
|
+
},
|
|
141
|
+
valueKey: this.valueKey,
|
|
142
|
+
labelKey: this.labelKey,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
114
145
|
renderChart() {
|
|
115
146
|
if (!this.plotArea || !this.svg || !this.plotGroup) {
|
|
116
147
|
throw new Error('Plot area not calculated');
|
package/grid.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type { GridConfig, ChartTheme, D3Scale } from './types.js';
|
|
2
|
+
import type { GridConfig, ChartTheme, D3Scale, ExportHooks, GridConfigBase } from './types.js';
|
|
3
3
|
import type { ChartComponent } from './chart-interface.js';
|
|
4
|
-
export declare class Grid implements ChartComponent {
|
|
4
|
+
export declare class Grid implements ChartComponent<GridConfigBase> {
|
|
5
5
|
readonly type: "grid";
|
|
6
6
|
readonly horizontal: boolean;
|
|
7
7
|
readonly vertical: boolean;
|
|
8
|
+
readonly exportHooks?: ExportHooks<GridConfigBase>;
|
|
8
9
|
constructor(config?: GridConfig);
|
|
10
|
+
getExportConfig(): GridConfigBase;
|
|
11
|
+
createExportComponent(override?: Partial<GridConfigBase>): ChartComponent;
|
|
9
12
|
render(plotGroup: Selection<SVGGElement, undefined, null, undefined>, x: D3Scale, y: D3Scale, theme: ChartTheme): void;
|
|
10
13
|
}
|
package/grid.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { axisBottom, axisLeft } from 'd3';
|
|
2
|
+
import { mergeDeep } from './utils.js';
|
|
2
3
|
export class Grid {
|
|
3
4
|
constructor(config) {
|
|
4
5
|
Object.defineProperty(this, "type", {
|
|
@@ -19,8 +20,28 @@ export class Grid {
|
|
|
19
20
|
writable: true,
|
|
20
21
|
value: void 0
|
|
21
22
|
});
|
|
23
|
+
Object.defineProperty(this, "exportHooks", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
writable: true,
|
|
27
|
+
value: void 0
|
|
28
|
+
});
|
|
22
29
|
this.horizontal = config?.horizontal ?? true;
|
|
23
30
|
this.vertical = config?.vertical ?? true;
|
|
31
|
+
this.exportHooks = config?.exportHooks;
|
|
32
|
+
}
|
|
33
|
+
getExportConfig() {
|
|
34
|
+
return {
|
|
35
|
+
horizontal: this.horizontal,
|
|
36
|
+
vertical: this.vertical,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
createExportComponent(override) {
|
|
40
|
+
const merged = mergeDeep(this.getExportConfig(), override);
|
|
41
|
+
return new Grid({
|
|
42
|
+
...merged,
|
|
43
|
+
exportHooks: this.exportHooks,
|
|
44
|
+
});
|
|
24
45
|
}
|
|
25
46
|
render(plotGroup, x, y, theme) {
|
|
26
47
|
// Get plot area dimensions from the scale ranges
|
package/legend.d.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type { LegendConfig, ChartTheme, LegendSeries } from './types.js';
|
|
2
|
+
import type { LegendConfig, ChartTheme, LegendSeries, ExportHooks, LegendConfigBase } from './types.js';
|
|
3
3
|
import type { LayoutAwareComponent, ComponentSpace } from './chart-interface.js';
|
|
4
|
-
export declare class Legend implements LayoutAwareComponent {
|
|
4
|
+
export declare class Legend implements LayoutAwareComponent<LegendConfigBase> {
|
|
5
5
|
readonly type: "legend";
|
|
6
6
|
readonly position: LegendConfig['position'];
|
|
7
|
+
readonly exportHooks?: ExportHooks<LegendConfigBase>;
|
|
7
8
|
private readonly marginTop;
|
|
8
9
|
private readonly marginBottom;
|
|
9
10
|
private readonly itemHeight;
|
|
10
11
|
private visibilityState;
|
|
11
12
|
private onToggleCallback?;
|
|
12
13
|
constructor(config?: LegendConfig);
|
|
14
|
+
getExportConfig(): LegendConfigBase;
|
|
15
|
+
createExportComponent(override?: Partial<LegendConfigBase>): LayoutAwareComponent;
|
|
13
16
|
setToggleCallback(callback: () => void): void;
|
|
14
17
|
isSeriesVisible(dataKey: string): boolean;
|
|
15
18
|
private getCheckmarkPath;
|
package/legend.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getSeriesColor } from './types.js';
|
|
2
|
+
import { mergeDeep } from './utils.js';
|
|
2
3
|
export class Legend {
|
|
3
4
|
constructor(config) {
|
|
4
5
|
Object.defineProperty(this, "type", {
|
|
@@ -13,6 +14,12 @@ export class Legend {
|
|
|
13
14
|
writable: true,
|
|
14
15
|
value: void 0
|
|
15
16
|
});
|
|
17
|
+
Object.defineProperty(this, "exportHooks", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true,
|
|
21
|
+
value: void 0
|
|
22
|
+
});
|
|
16
23
|
Object.defineProperty(this, "marginTop", {
|
|
17
24
|
enumerable: true,
|
|
18
25
|
configurable: true,
|
|
@@ -46,6 +53,23 @@ export class Legend {
|
|
|
46
53
|
this.position = config?.position || 'bottom';
|
|
47
54
|
this.marginTop = config?.marginTop ?? 20;
|
|
48
55
|
this.marginBottom = config?.marginBottom ?? 10;
|
|
56
|
+
this.exportHooks = config?.exportHooks;
|
|
57
|
+
}
|
|
58
|
+
getExportConfig() {
|
|
59
|
+
return {
|
|
60
|
+
position: this.position,
|
|
61
|
+
marginTop: this.marginTop,
|
|
62
|
+
marginBottom: this.marginBottom,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
createExportComponent(override) {
|
|
66
|
+
const merged = mergeDeep(this.getExportConfig(), override);
|
|
67
|
+
const legend = new Legend({
|
|
68
|
+
...merged,
|
|
69
|
+
exportHooks: this.exportHooks,
|
|
70
|
+
});
|
|
71
|
+
legend.visibilityState = new Map(this.visibilityState);
|
|
72
|
+
return legend;
|
|
49
73
|
}
|
|
50
74
|
setToggleCallback(callback) {
|
|
51
75
|
this.onToggleCallback = callback;
|
package/line.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type { LineConfig, DataItem, D3Scale, ScaleType, ChartTheme, LineValueLabelConfig } from './types.js';
|
|
2
|
+
import type { LineConfig, DataItem, D3Scale, ScaleType, ChartTheme, LineValueLabelConfig, ExportHooks, LineConfigBase } from './types.js';
|
|
3
3
|
import type { ChartComponent } from './chart-interface.js';
|
|
4
|
-
export declare class Line implements ChartComponent {
|
|
4
|
+
export declare class Line implements ChartComponent<LineConfigBase> {
|
|
5
5
|
readonly type: "line";
|
|
6
6
|
readonly dataKey: string;
|
|
7
7
|
readonly stroke: string;
|
|
8
8
|
readonly strokeWidth?: number;
|
|
9
9
|
readonly valueLabel?: LineValueLabelConfig;
|
|
10
|
+
readonly exportHooks?: ExportHooks<LineConfigBase>;
|
|
10
11
|
constructor(config: LineConfig);
|
|
12
|
+
getExportConfig(): LineConfigBase;
|
|
13
|
+
createExportComponent(override?: Partial<LineConfigBase>): ChartComponent;
|
|
11
14
|
render(plotGroup: Selection<SVGGElement, undefined, null, undefined>, data: DataItem[], xKey: string, x: D3Scale, y: D3Scale, parseValue: (value: unknown) => number, xScaleType: ScaleType | undefined, theme: ChartTheme): void;
|
|
12
15
|
private renderValueLabels;
|
|
13
16
|
}
|
package/line.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { line } from 'd3';
|
|
2
|
-
import { sanitizeForCSS } from './utils.js';
|
|
2
|
+
import { sanitizeForCSS, mergeDeep } from './utils.js';
|
|
3
3
|
export class Line {
|
|
4
4
|
constructor(config) {
|
|
5
5
|
Object.defineProperty(this, "type", {
|
|
@@ -32,10 +32,32 @@ export class Line {
|
|
|
32
32
|
writable: true,
|
|
33
33
|
value: void 0
|
|
34
34
|
});
|
|
35
|
+
Object.defineProperty(this, "exportHooks", {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
writable: true,
|
|
39
|
+
value: void 0
|
|
40
|
+
});
|
|
35
41
|
this.dataKey = config.dataKey;
|
|
36
42
|
this.stroke = config.stroke || '#8884d8';
|
|
37
43
|
this.strokeWidth = config.strokeWidth;
|
|
38
44
|
this.valueLabel = config.valueLabel;
|
|
45
|
+
this.exportHooks = config.exportHooks;
|
|
46
|
+
}
|
|
47
|
+
getExportConfig() {
|
|
48
|
+
return {
|
|
49
|
+
dataKey: this.dataKey,
|
|
50
|
+
stroke: this.stroke,
|
|
51
|
+
strokeWidth: this.strokeWidth,
|
|
52
|
+
valueLabel: this.valueLabel,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
createExportComponent(override) {
|
|
56
|
+
const merged = mergeDeep(this.getExportConfig(), override);
|
|
57
|
+
return new Line({
|
|
58
|
+
...merged,
|
|
59
|
+
exportHooks: this.exportHooks,
|
|
60
|
+
});
|
|
39
61
|
}
|
|
40
62
|
render(plotGroup, data, xKey, x, y, parseValue, xScaleType = 'band', theme) {
|
|
41
63
|
const getXPosition = (d) => {
|
package/package.json
CHANGED
package/title.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type { TitleConfig, ChartTheme } from './types.js';
|
|
2
|
+
import type { TitleConfig, ChartTheme, ExportHooks, TitleConfigBase } from './types.js';
|
|
3
3
|
import type { LayoutAwareComponent, ComponentSpace } from './chart-interface.js';
|
|
4
|
-
export declare class Title implements LayoutAwareComponent {
|
|
4
|
+
export declare class Title implements LayoutAwareComponent<TitleConfigBase> {
|
|
5
5
|
readonly type: "title";
|
|
6
6
|
readonly text: string;
|
|
7
|
+
readonly exportHooks?: ExportHooks<TitleConfigBase>;
|
|
7
8
|
private readonly fontSize;
|
|
8
9
|
private readonly fontWeight;
|
|
9
10
|
private readonly fontFamily?;
|
|
@@ -11,6 +12,8 @@ export declare class Title implements LayoutAwareComponent {
|
|
|
11
12
|
private readonly marginTop;
|
|
12
13
|
private readonly marginBottom;
|
|
13
14
|
constructor(config: TitleConfig);
|
|
15
|
+
getExportConfig(): TitleConfigBase;
|
|
16
|
+
createExportComponent(override?: Partial<TitleConfigBase>): LayoutAwareComponent;
|
|
14
17
|
/**
|
|
15
18
|
* Returns the space required by the title
|
|
16
19
|
*/
|
package/title.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mergeDeep } from './utils.js';
|
|
1
2
|
export class Title {
|
|
2
3
|
constructor(config) {
|
|
3
4
|
Object.defineProperty(this, "type", {
|
|
@@ -12,6 +13,12 @@ export class Title {
|
|
|
12
13
|
writable: true,
|
|
13
14
|
value: void 0
|
|
14
15
|
});
|
|
16
|
+
Object.defineProperty(this, "exportHooks", {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
value: void 0
|
|
21
|
+
});
|
|
15
22
|
Object.defineProperty(this, "fontSize", {
|
|
16
23
|
enumerable: true,
|
|
17
24
|
configurable: true,
|
|
@@ -55,6 +62,25 @@ export class Title {
|
|
|
55
62
|
this.align = config.align ?? 'center';
|
|
56
63
|
this.marginTop = config.marginTop ?? 10;
|
|
57
64
|
this.marginBottom = config.marginBottom ?? 15;
|
|
65
|
+
this.exportHooks = config.exportHooks;
|
|
66
|
+
}
|
|
67
|
+
getExportConfig() {
|
|
68
|
+
return {
|
|
69
|
+
text: this.text,
|
|
70
|
+
fontSize: this.fontSize,
|
|
71
|
+
fontWeight: this.fontWeight,
|
|
72
|
+
fontFamily: this.fontFamily,
|
|
73
|
+
align: this.align,
|
|
74
|
+
marginTop: this.marginTop,
|
|
75
|
+
marginBottom: this.marginBottom,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
createExportComponent(override) {
|
|
79
|
+
const merged = mergeDeep(this.getExportConfig(), override);
|
|
80
|
+
return new Title({
|
|
81
|
+
...merged,
|
|
82
|
+
exportHooks: this.exportHooks,
|
|
83
|
+
});
|
|
58
84
|
}
|
|
59
85
|
/**
|
|
60
86
|
* Returns the space required by the title
|
package/tooltip.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type { TooltipConfig, DataItem, DataValue, D3Scale, ChartTheme } from './types.js';
|
|
2
|
+
import type { TooltipConfig, DataItem, DataValue, D3Scale, ChartTheme, ExportHooks, TooltipConfigBase } from './types.js';
|
|
3
3
|
import type { ChartComponent } from './chart-interface.js';
|
|
4
4
|
import type { Line } from './line.js';
|
|
5
5
|
import type { Bar } from './bar.js';
|
|
6
6
|
import type { PlotAreaBounds } from './layout-manager.js';
|
|
7
|
-
export declare class Tooltip implements ChartComponent {
|
|
7
|
+
export declare class Tooltip implements ChartComponent<TooltipConfigBase> {
|
|
8
8
|
readonly id = "iisChartTooltip";
|
|
9
9
|
readonly type: "tooltip";
|
|
10
10
|
readonly formatter?: (dataKey: string, value: DataValue, data: DataItem) => string;
|
|
@@ -14,8 +14,11 @@ export declare class Tooltip implements ChartComponent {
|
|
|
14
14
|
stroke?: string;
|
|
15
15
|
fill?: string;
|
|
16
16
|
}[]) => string;
|
|
17
|
+
readonly exportHooks?: ExportHooks<TooltipConfigBase>;
|
|
17
18
|
private tooltipDiv;
|
|
18
19
|
constructor(config?: TooltipConfig);
|
|
20
|
+
getExportConfig(): TooltipConfigBase;
|
|
21
|
+
createExportComponent(override?: Partial<TooltipConfigBase>): ChartComponent;
|
|
19
22
|
initialize(theme: ChartTheme): void;
|
|
20
23
|
attachToArea(svg: Selection<SVGSVGElement, undefined, null, undefined>, data: DataItem[], series: (Line | Bar)[], xKey: string, x: D3Scale, y: D3Scale, theme: ChartTheme, plotArea: PlotAreaBounds, parseValue: (value: unknown) => number, isHorizontal?: boolean): void;
|
|
21
24
|
cleanup(): void;
|
package/tooltip.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { pointer, select } from 'd3';
|
|
2
2
|
import { getSeriesColor } from './types.js';
|
|
3
|
-
import { sanitizeForCSS } from './utils.js';
|
|
3
|
+
import { sanitizeForCSS, mergeDeep } from './utils.js';
|
|
4
4
|
export class Tooltip {
|
|
5
5
|
constructor(config) {
|
|
6
6
|
Object.defineProperty(this, "id", {
|
|
@@ -33,6 +33,12 @@ export class Tooltip {
|
|
|
33
33
|
writable: true,
|
|
34
34
|
value: void 0
|
|
35
35
|
});
|
|
36
|
+
Object.defineProperty(this, "exportHooks", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
configurable: true,
|
|
39
|
+
writable: true,
|
|
40
|
+
value: void 0
|
|
41
|
+
});
|
|
36
42
|
Object.defineProperty(this, "tooltipDiv", {
|
|
37
43
|
enumerable: true,
|
|
38
44
|
configurable: true,
|
|
@@ -42,6 +48,21 @@ export class Tooltip {
|
|
|
42
48
|
this.formatter = config?.formatter;
|
|
43
49
|
this.labelFormatter = config?.labelFormatter;
|
|
44
50
|
this.customFormatter = config?.customFormatter;
|
|
51
|
+
this.exportHooks = config?.exportHooks;
|
|
52
|
+
}
|
|
53
|
+
getExportConfig() {
|
|
54
|
+
return {
|
|
55
|
+
formatter: this.formatter,
|
|
56
|
+
labelFormatter: this.labelFormatter,
|
|
57
|
+
customFormatter: this.customFormatter,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
createExportComponent(override) {
|
|
61
|
+
const merged = mergeDeep(this.getExportConfig(), override);
|
|
62
|
+
return new Tooltip({
|
|
63
|
+
...merged,
|
|
64
|
+
exportHooks: this.exportHooks,
|
|
65
|
+
});
|
|
45
66
|
}
|
|
46
67
|
initialize(theme) {
|
|
47
68
|
this.cleanup();
|
package/types.d.ts
CHANGED
|
@@ -4,6 +4,21 @@ export type ExportFormat = 'svg' | 'json';
|
|
|
4
4
|
export type ExportOptions = {
|
|
5
5
|
download?: boolean;
|
|
6
6
|
filename?: string;
|
|
7
|
+
width?: number;
|
|
8
|
+
height?: number;
|
|
9
|
+
};
|
|
10
|
+
export type ExportRenderContext = {
|
|
11
|
+
format: ExportFormat;
|
|
12
|
+
options?: ExportOptions;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
};
|
|
16
|
+
export type ExportHookContext = ExportRenderContext & {
|
|
17
|
+
svg: SVGSVGElement;
|
|
18
|
+
};
|
|
19
|
+
export type ExportHooks<TConfig = Record<string, unknown>> = {
|
|
20
|
+
beforeRender?: (context: ExportRenderContext, currentConfig: TConfig) => void | Partial<TConfig>;
|
|
21
|
+
before?: (context: ExportHookContext) => void;
|
|
7
22
|
};
|
|
8
23
|
export type ColorPalette = string[];
|
|
9
24
|
export type ChartTheme = {
|
|
@@ -94,13 +109,16 @@ export type BarValueLabelConfig = ValueLabelConfig & {
|
|
|
94
109
|
position?: 'inside' | 'outside';
|
|
95
110
|
insidePosition?: 'top' | 'middle' | 'bottom';
|
|
96
111
|
};
|
|
97
|
-
export type
|
|
112
|
+
export type LineConfigBase = {
|
|
98
113
|
dataKey: string;
|
|
99
114
|
stroke?: string;
|
|
100
115
|
strokeWidth?: number;
|
|
101
116
|
valueLabel?: LineValueLabelConfig;
|
|
102
117
|
};
|
|
103
|
-
export type
|
|
118
|
+
export type LineConfig = LineConfigBase & {
|
|
119
|
+
exportHooks?: ExportHooks<LineConfigBase>;
|
|
120
|
+
};
|
|
121
|
+
export type BarConfigBase = {
|
|
104
122
|
dataKey: string;
|
|
105
123
|
fill?: string;
|
|
106
124
|
colorAdapter?: (data: DataItem, index: number) => string;
|
|
@@ -108,6 +126,9 @@ export type BarConfig = {
|
|
|
108
126
|
maxBarSize?: number;
|
|
109
127
|
valueLabel?: BarValueLabelConfig;
|
|
110
128
|
};
|
|
129
|
+
export type BarConfig = BarConfigBase & {
|
|
130
|
+
exportHooks?: ExportHooks<BarConfigBase>;
|
|
131
|
+
};
|
|
111
132
|
export type BarStackConfig = {
|
|
112
133
|
mode?: BarStackMode;
|
|
113
134
|
gap?: number;
|
|
@@ -117,7 +138,7 @@ export declare function getSeriesColor(series: {
|
|
|
117
138
|
fill?: string;
|
|
118
139
|
}): string;
|
|
119
140
|
export type LabelOversizedBehavior = 'truncate' | 'wrap' | 'hide';
|
|
120
|
-
export type
|
|
141
|
+
export type XAxisConfigBase = {
|
|
121
142
|
dataKey?: string;
|
|
122
143
|
rotatedLabels?: boolean;
|
|
123
144
|
maxLabelWidth?: number;
|
|
@@ -127,17 +148,26 @@ export type XAxisConfig = {
|
|
|
127
148
|
minLabelGap?: number;
|
|
128
149
|
preserveEndLabels?: boolean;
|
|
129
150
|
};
|
|
130
|
-
export type
|
|
151
|
+
export type XAxisConfig = XAxisConfigBase & {
|
|
152
|
+
exportHooks?: ExportHooks<XAxisConfigBase>;
|
|
153
|
+
};
|
|
154
|
+
export type YAxisConfigBase = {
|
|
131
155
|
tickFormat?: string | ((value: number) => string) | null;
|
|
132
156
|
rotatedLabels?: boolean;
|
|
133
157
|
maxLabelWidth?: number;
|
|
134
158
|
oversizedBehavior?: LabelOversizedBehavior;
|
|
135
159
|
};
|
|
136
|
-
export type
|
|
160
|
+
export type YAxisConfig = YAxisConfigBase & {
|
|
161
|
+
exportHooks?: ExportHooks<YAxisConfigBase>;
|
|
162
|
+
};
|
|
163
|
+
export type GridConfigBase = {
|
|
137
164
|
horizontal?: boolean;
|
|
138
165
|
vertical?: boolean;
|
|
139
166
|
};
|
|
140
|
-
export type
|
|
167
|
+
export type GridConfig = GridConfigBase & {
|
|
168
|
+
exportHooks?: ExportHooks<GridConfigBase>;
|
|
169
|
+
};
|
|
170
|
+
export type TooltipConfigBase = {
|
|
141
171
|
formatter?: (dataKey: string, value: DataValue, data: DataItem) => string;
|
|
142
172
|
labelFormatter?: (label: string, data: DataItem) => string;
|
|
143
173
|
customFormatter?: (data: DataItem, series: {
|
|
@@ -146,17 +176,23 @@ export type TooltipConfig = {
|
|
|
146
176
|
fill?: string;
|
|
147
177
|
}[]) => string;
|
|
148
178
|
};
|
|
149
|
-
export type
|
|
179
|
+
export type TooltipConfig = TooltipConfigBase & {
|
|
180
|
+
exportHooks?: ExportHooks<TooltipConfigBase>;
|
|
181
|
+
};
|
|
182
|
+
export type LegendConfigBase = {
|
|
150
183
|
position?: 'bottom';
|
|
151
184
|
marginTop?: number;
|
|
152
185
|
marginBottom?: number;
|
|
153
186
|
};
|
|
187
|
+
export type LegendConfig = LegendConfigBase & {
|
|
188
|
+
exportHooks?: ExportHooks<LegendConfigBase>;
|
|
189
|
+
};
|
|
154
190
|
export type LegendSeries = {
|
|
155
191
|
dataKey: string;
|
|
156
192
|
stroke?: string;
|
|
157
193
|
fill?: string;
|
|
158
194
|
};
|
|
159
|
-
export type
|
|
195
|
+
export type TitleConfigBase = {
|
|
160
196
|
text: string;
|
|
161
197
|
fontSize?: number;
|
|
162
198
|
fontWeight?: string;
|
|
@@ -165,6 +201,9 @@ export type TitleConfig = {
|
|
|
165
201
|
marginTop?: number;
|
|
166
202
|
marginBottom?: number;
|
|
167
203
|
};
|
|
204
|
+
export type TitleConfig = TitleConfigBase & {
|
|
205
|
+
exportHooks?: ExportHooks<TitleConfigBase>;
|
|
206
|
+
};
|
|
168
207
|
export type ScaleType = 'band' | 'linear' | 'time' | 'log';
|
|
169
208
|
export type D3Scale = any;
|
|
170
209
|
export type ScaleDomainValue = string | number | Date;
|
package/utils.d.ts
CHANGED
|
@@ -60,3 +60,4 @@ export declare function breakWord(word: string, maxWidth: number, fontSize: stri
|
|
|
60
60
|
* @returns Array of lines
|
|
61
61
|
*/
|
|
62
62
|
export declare function wrapText(text: string, maxWidth: number, fontSize: string | number, fontFamily: string, fontWeight: string, svg: SVGSVGElement): string[];
|
|
63
|
+
export declare function mergeDeep<T extends Record<string, unknown>>(base: T, override?: Partial<T>): T;
|
package/utils.js
CHANGED
|
@@ -168,3 +168,25 @@ export function wrapText(text, maxWidth, fontSize, fontFamily, fontWeight, svg)
|
|
|
168
168
|
}
|
|
169
169
|
return lines.length > 0 ? lines : [text];
|
|
170
170
|
}
|
|
171
|
+
export function mergeDeep(base, override) {
|
|
172
|
+
if (!override) {
|
|
173
|
+
return { ...base };
|
|
174
|
+
}
|
|
175
|
+
const result = { ...base };
|
|
176
|
+
Object.keys(override).forEach((key) => {
|
|
177
|
+
const overrideValue = override[key];
|
|
178
|
+
const baseValue = base[key];
|
|
179
|
+
if (overrideValue &&
|
|
180
|
+
typeof overrideValue === 'object' &&
|
|
181
|
+
!Array.isArray(overrideValue) &&
|
|
182
|
+
baseValue &&
|
|
183
|
+
typeof baseValue === 'object' &&
|
|
184
|
+
!Array.isArray(baseValue)) {
|
|
185
|
+
result[key] = mergeDeep(baseValue, overrideValue);
|
|
186
|
+
}
|
|
187
|
+
else if (overrideValue !== undefined) {
|
|
188
|
+
result[key] = overrideValue;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
return result;
|
|
192
|
+
}
|
package/x-axis.d.ts
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type { XAxisConfig, ChartTheme, D3Scale } from './types.js';
|
|
2
|
+
import type { XAxisConfig, ChartTheme, D3Scale, ExportHooks, XAxisConfigBase } from './types.js';
|
|
3
3
|
import type { LayoutAwareComponent, ComponentSpace } from './chart-interface.js';
|
|
4
|
-
export declare class XAxis implements LayoutAwareComponent {
|
|
4
|
+
export declare class XAxis implements LayoutAwareComponent<XAxisConfigBase> {
|
|
5
5
|
readonly type: "xAxis";
|
|
6
6
|
readonly dataKey?: string;
|
|
7
7
|
private readonly rotatedLabels;
|
|
8
8
|
private readonly tickPadding;
|
|
9
|
-
private
|
|
9
|
+
private fontSize;
|
|
10
10
|
private readonly maxLabelWidth?;
|
|
11
11
|
private readonly oversizedBehavior;
|
|
12
12
|
private readonly tickFormat;
|
|
13
13
|
private wrapLineCount;
|
|
14
|
+
private estimatedHeight;
|
|
14
15
|
private readonly autoHideOverlapping;
|
|
15
16
|
private readonly minLabelGap;
|
|
16
17
|
private readonly preserveEndLabels;
|
|
18
|
+
readonly exportHooks?: ExportHooks<XAxisConfigBase>;
|
|
17
19
|
constructor(config?: XAxisConfig);
|
|
20
|
+
getExportConfig(): XAxisConfigBase;
|
|
21
|
+
createExportComponent(override?: Partial<XAxisConfigBase>): LayoutAwareComponent;
|
|
18
22
|
/**
|
|
19
23
|
* Returns the space required by the x-axis
|
|
20
24
|
*/
|
|
21
25
|
getRequiredSpace(): ComponentSpace;
|
|
26
|
+
estimateLayoutSpace(labels: unknown[], theme: ChartTheme, svg: SVGSVGElement): void;
|
|
27
|
+
clearEstimatedSpace(): void;
|
|
22
28
|
render(svg: Selection<SVGSVGElement, undefined, null, undefined>, x: D3Scale, theme: ChartTheme, yPosition: number): void;
|
|
23
29
|
private applyLabelConstraints;
|
|
24
30
|
private wrapTextElement;
|
package/x-axis.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { axisBottom } from 'd3';
|
|
2
|
-
import { measureTextWidth, truncateText, wrapText } from './utils.js';
|
|
2
|
+
import { measureTextWidth, truncateText, wrapText, mergeDeep } from './utils.js';
|
|
3
3
|
export class XAxis {
|
|
4
4
|
constructor(config) {
|
|
5
5
|
Object.defineProperty(this, "type", {
|
|
@@ -57,6 +57,12 @@ export class XAxis {
|
|
|
57
57
|
writable: true,
|
|
58
58
|
value: 1
|
|
59
59
|
});
|
|
60
|
+
Object.defineProperty(this, "estimatedHeight", {
|
|
61
|
+
enumerable: true,
|
|
62
|
+
configurable: true,
|
|
63
|
+
writable: true,
|
|
64
|
+
value: null
|
|
65
|
+
});
|
|
60
66
|
Object.defineProperty(this, "autoHideOverlapping", {
|
|
61
67
|
enumerable: true,
|
|
62
68
|
configurable: true,
|
|
@@ -75,6 +81,12 @@ export class XAxis {
|
|
|
75
81
|
writable: true,
|
|
76
82
|
value: void 0
|
|
77
83
|
});
|
|
84
|
+
Object.defineProperty(this, "exportHooks", {
|
|
85
|
+
enumerable: true,
|
|
86
|
+
configurable: true,
|
|
87
|
+
writable: true,
|
|
88
|
+
value: void 0
|
|
89
|
+
});
|
|
78
90
|
this.dataKey = config?.dataKey;
|
|
79
91
|
this.rotatedLabels = config?.rotatedLabels ?? false;
|
|
80
92
|
this.maxLabelWidth = config?.maxLabelWidth;
|
|
@@ -83,11 +95,38 @@ export class XAxis {
|
|
|
83
95
|
this.autoHideOverlapping = config?.autoHideOverlapping ?? false;
|
|
84
96
|
this.minLabelGap = config?.minLabelGap ?? 8;
|
|
85
97
|
this.preserveEndLabels = config?.preserveEndLabels ?? true;
|
|
98
|
+
this.exportHooks = config?.exportHooks;
|
|
99
|
+
}
|
|
100
|
+
getExportConfig() {
|
|
101
|
+
return {
|
|
102
|
+
dataKey: this.dataKey,
|
|
103
|
+
rotatedLabels: this.rotatedLabels,
|
|
104
|
+
maxLabelWidth: this.maxLabelWidth,
|
|
105
|
+
oversizedBehavior: this.oversizedBehavior,
|
|
106
|
+
tickFormat: this.tickFormat,
|
|
107
|
+
autoHideOverlapping: this.autoHideOverlapping,
|
|
108
|
+
minLabelGap: this.minLabelGap,
|
|
109
|
+
preserveEndLabels: this.preserveEndLabels,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
createExportComponent(override) {
|
|
113
|
+
const merged = mergeDeep(this.getExportConfig(), override);
|
|
114
|
+
return new XAxis({
|
|
115
|
+
...merged,
|
|
116
|
+
exportHooks: this.exportHooks,
|
|
117
|
+
});
|
|
86
118
|
}
|
|
87
119
|
/**
|
|
88
120
|
* Returns the space required by the x-axis
|
|
89
121
|
*/
|
|
90
122
|
getRequiredSpace() {
|
|
123
|
+
if (this.estimatedHeight !== null) {
|
|
124
|
+
return {
|
|
125
|
+
width: 0,
|
|
126
|
+
height: this.estimatedHeight,
|
|
127
|
+
position: 'bottom',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
91
130
|
// Height = tick padding + font size + some extra space for descenders
|
|
92
131
|
// Rotated labels need more vertical space (roughly 2.5x for -45deg rotation)
|
|
93
132
|
const baseHeight = this.tickPadding + this.fontSize + 5;
|
|
@@ -104,6 +143,53 @@ export class XAxis {
|
|
|
104
143
|
position: 'bottom',
|
|
105
144
|
};
|
|
106
145
|
}
|
|
146
|
+
estimateLayoutSpace(labels, theme, svg) {
|
|
147
|
+
if (!labels.length) {
|
|
148
|
+
this.estimatedHeight = null;
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const parsedFontSize = typeof theme.axis.fontSize === 'string'
|
|
152
|
+
? parseFloat(theme.axis.fontSize)
|
|
153
|
+
: theme.axis.fontSize;
|
|
154
|
+
this.fontSize = Number.isFinite(parsedFontSize)
|
|
155
|
+
? parsedFontSize
|
|
156
|
+
: this.fontSize;
|
|
157
|
+
const fontSize = this.fontSize;
|
|
158
|
+
const fontFamily = theme.axis.fontFamily;
|
|
159
|
+
const fontWeight = theme.axis.fontWeight || 'normal';
|
|
160
|
+
let maxWidth = 0;
|
|
161
|
+
let maxLines = 1;
|
|
162
|
+
for (const label of labels) {
|
|
163
|
+
const text = String(label ?? '');
|
|
164
|
+
if (!text)
|
|
165
|
+
continue;
|
|
166
|
+
const textWidth = measureTextWidth(text, fontSize, fontFamily, fontWeight, svg);
|
|
167
|
+
if (this.maxLabelWidth && this.oversizedBehavior === 'wrap') {
|
|
168
|
+
const lines = wrapText(text, this.maxLabelWidth, fontSize, fontFamily, fontWeight, svg);
|
|
169
|
+
maxLines = Math.max(maxLines, lines.length || 1);
|
|
170
|
+
maxWidth = Math.max(maxWidth, this.maxLabelWidth);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const effectiveWidth = this.maxLabelWidth
|
|
174
|
+
? Math.min(textWidth, this.maxLabelWidth)
|
|
175
|
+
: textWidth;
|
|
176
|
+
maxWidth = Math.max(maxWidth, effectiveWidth);
|
|
177
|
+
}
|
|
178
|
+
const lineHeight = fontSize * 1.2;
|
|
179
|
+
const textHeight = lineHeight * maxLines;
|
|
180
|
+
if (this.rotatedLabels) {
|
|
181
|
+
const radians = Math.PI / 4;
|
|
182
|
+
const verticalFootprint = Math.sin(radians) * maxWidth + Math.cos(radians) * textHeight;
|
|
183
|
+
this.estimatedHeight = this.tickPadding + verticalFootprint + 5;
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
this.estimatedHeight = this.tickPadding + textHeight + 5;
|
|
187
|
+
}
|
|
188
|
+
this.wrapLineCount = Math.max(this.wrapLineCount, maxLines);
|
|
189
|
+
}
|
|
190
|
+
clearEstimatedSpace() {
|
|
191
|
+
this.estimatedHeight = null;
|
|
192
|
+
}
|
|
107
193
|
render(svg, x, theme, yPosition) {
|
|
108
194
|
const axisGenerator = axisBottom(x)
|
|
109
195
|
.tickSizeOuter(0)
|
package/xy-chart.d.ts
CHANGED
|
@@ -10,7 +10,10 @@ export declare class XYChart extends BaseChart {
|
|
|
10
10
|
private barStackGap;
|
|
11
11
|
constructor(config: XYChartConfig);
|
|
12
12
|
addChild(component: ChartComponent): this;
|
|
13
|
+
protected getExportComponents(): ChartComponent[];
|
|
14
|
+
protected createExportChart(): BaseChart;
|
|
13
15
|
private rerender;
|
|
16
|
+
protected prepareLayout(): void;
|
|
14
17
|
protected renderChart(): void;
|
|
15
18
|
private getXKey;
|
|
16
19
|
private setupScales;
|
package/xy-chart.js
CHANGED
|
@@ -68,9 +68,51 @@ export class XYChart extends BaseChart {
|
|
|
68
68
|
}
|
|
69
69
|
return this;
|
|
70
70
|
}
|
|
71
|
+
getExportComponents() {
|
|
72
|
+
const components = [];
|
|
73
|
+
if (this.title) {
|
|
74
|
+
components.push(this.title);
|
|
75
|
+
}
|
|
76
|
+
if (this.grid) {
|
|
77
|
+
components.push(this.grid);
|
|
78
|
+
}
|
|
79
|
+
components.push(...this.series);
|
|
80
|
+
if (this.xAxis) {
|
|
81
|
+
components.push(this.xAxis);
|
|
82
|
+
}
|
|
83
|
+
if (this.yAxis) {
|
|
84
|
+
components.push(this.yAxis);
|
|
85
|
+
}
|
|
86
|
+
if (this.tooltip) {
|
|
87
|
+
components.push(this.tooltip);
|
|
88
|
+
}
|
|
89
|
+
if (this.legend) {
|
|
90
|
+
components.push(this.legend);
|
|
91
|
+
}
|
|
92
|
+
return components;
|
|
93
|
+
}
|
|
94
|
+
createExportChart() {
|
|
95
|
+
return new XYChart({
|
|
96
|
+
data: this.data,
|
|
97
|
+
theme: this.theme,
|
|
98
|
+
scales: this.scaleConfig,
|
|
99
|
+
barStack: {
|
|
100
|
+
mode: this.barStackMode,
|
|
101
|
+
gap: this.barStackGap,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
71
105
|
rerender() {
|
|
72
106
|
this.update(this.data);
|
|
73
107
|
}
|
|
108
|
+
prepareLayout() {
|
|
109
|
+
this.xAxis?.clearEstimatedSpace?.();
|
|
110
|
+
if (!this.xAxis || !this.svg)
|
|
111
|
+
return;
|
|
112
|
+
const xKey = this.getXKey();
|
|
113
|
+
const labels = this.data.map((item) => item[xKey]);
|
|
114
|
+
this.xAxis.estimateLayoutSpace?.(labels, this.theme, this.svg.node());
|
|
115
|
+
}
|
|
74
116
|
renderChart() {
|
|
75
117
|
if (!this.plotArea) {
|
|
76
118
|
throw new Error('Plot area not calculated');
|
package/y-axis.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type { ChartTheme, YAxisConfig, D3Scale } from './types.js';
|
|
2
|
+
import type { ChartTheme, YAxisConfig, D3Scale, ExportHooks, YAxisConfigBase } from './types.js';
|
|
3
3
|
import type { LayoutAwareComponent, ComponentSpace } from './chart-interface.js';
|
|
4
|
-
export declare class YAxis implements LayoutAwareComponent {
|
|
4
|
+
export declare class YAxis implements LayoutAwareComponent<YAxisConfigBase> {
|
|
5
5
|
readonly type: "yAxis";
|
|
6
6
|
private readonly tickPadding;
|
|
7
7
|
private readonly fontSize;
|
|
@@ -9,7 +9,10 @@ export declare class YAxis implements LayoutAwareComponent {
|
|
|
9
9
|
private readonly tickFormat;
|
|
10
10
|
private readonly rotatedLabels;
|
|
11
11
|
private readonly oversizedBehavior;
|
|
12
|
+
readonly exportHooks?: ExportHooks<YAxisConfigBase>;
|
|
12
13
|
constructor(config?: YAxisConfig);
|
|
14
|
+
getExportConfig(): YAxisConfigBase;
|
|
15
|
+
createExportComponent(override?: Partial<YAxisConfigBase>): LayoutAwareComponent;
|
|
13
16
|
/**
|
|
14
17
|
* Returns the space required by the y-axis
|
|
15
18
|
*/
|
package/y-axis.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { axisLeft } from 'd3';
|
|
2
|
-
import { measureTextWidth, truncateText, wrapText } from './utils.js';
|
|
2
|
+
import { measureTextWidth, truncateText, wrapText, mergeDeep } from './utils.js';
|
|
3
3
|
export class YAxis {
|
|
4
4
|
constructor(config) {
|
|
5
5
|
Object.defineProperty(this, "type", {
|
|
@@ -45,10 +45,32 @@ export class YAxis {
|
|
|
45
45
|
writable: true,
|
|
46
46
|
value: void 0
|
|
47
47
|
});
|
|
48
|
+
Object.defineProperty(this, "exportHooks", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
configurable: true,
|
|
51
|
+
writable: true,
|
|
52
|
+
value: void 0
|
|
53
|
+
});
|
|
48
54
|
this.tickFormat = config?.tickFormat ?? null;
|
|
49
55
|
this.rotatedLabels = config?.rotatedLabels ?? false;
|
|
50
56
|
this.maxLabelWidth = config?.maxLabelWidth ?? 40; // Default 40 for backward compatibility
|
|
51
57
|
this.oversizedBehavior = config?.oversizedBehavior ?? 'truncate';
|
|
58
|
+
this.exportHooks = config?.exportHooks;
|
|
59
|
+
}
|
|
60
|
+
getExportConfig() {
|
|
61
|
+
return {
|
|
62
|
+
tickFormat: this.tickFormat,
|
|
63
|
+
rotatedLabels: this.rotatedLabels,
|
|
64
|
+
maxLabelWidth: this.maxLabelWidth,
|
|
65
|
+
oversizedBehavior: this.oversizedBehavior,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
createExportComponent(override) {
|
|
69
|
+
const merged = mergeDeep(this.getExportConfig(), override);
|
|
70
|
+
return new YAxis({
|
|
71
|
+
...merged,
|
|
72
|
+
exportHooks: this.exportHooks,
|
|
73
|
+
});
|
|
52
74
|
}
|
|
53
75
|
/**
|
|
54
76
|
* Returns the space required by the y-axis
|