@internetstiftelsen/charts 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -1
- package/dist/area.d.ts +11 -1
- package/dist/area.js +199 -55
- package/dist/bar.d.ts +26 -1
- package/dist/bar.js +425 -306
- package/dist/base-chart.d.ts +5 -0
- package/dist/base-chart.js +91 -67
- package/dist/chart-group.d.ts +16 -0
- package/dist/chart-group.js +201 -143
- package/dist/donut-center-content.d.ts +1 -0
- package/dist/donut-center-content.js +21 -38
- package/dist/donut-chart.js +32 -32
- package/dist/gauge-chart.d.ts +23 -4
- package/dist/gauge-chart.js +235 -185
- package/dist/lazy-mount.d.ts +13 -0
- package/dist/lazy-mount.js +90 -0
- package/dist/legend.js +10 -9
- package/dist/line.d.ts +9 -1
- package/dist/line.js +144 -24
- package/dist/pie-chart.d.ts +3 -0
- package/dist/pie-chart.js +49 -47
- package/dist/radial-chart-base.d.ts +4 -3
- package/dist/radial-chart-base.js +27 -12
- package/dist/scatter.d.ts +5 -1
- package/dist/scatter.js +92 -9
- package/dist/theme.js +17 -0
- package/dist/tooltip.d.ts +55 -3
- package/dist/tooltip.js +968 -159
- package/dist/types.d.ts +23 -1
- package/dist/utils.js +11 -19
- package/dist/x-axis.d.ts +10 -0
- package/dist/x-axis.js +190 -149
- package/dist/xy-animation.d.ts +3 -0
- package/dist/xy-animation.js +2 -0
- package/dist/xy-chart.d.ts +35 -1
- package/dist/xy-chart.js +358 -153
- package/dist/xy-motion/config.d.ts +2 -0
- package/dist/xy-motion/config.js +177 -0
- package/dist/xy-motion/driver.d.ts +9 -0
- package/dist/xy-motion/driver.js +10 -0
- package/dist/xy-motion/helpers.d.ts +17 -0
- package/dist/xy-motion/helpers.js +105 -0
- package/dist/xy-motion/live-state.d.ts +8 -0
- package/dist/xy-motion/live-state.js +240 -0
- package/dist/xy-motion/noop-xy-motion-driver.d.ts +9 -0
- package/dist/xy-motion/noop-xy-motion-driver.js +15 -0
- package/dist/xy-motion/types.d.ts +85 -0
- package/dist/xy-motion/types.js +1 -0
- package/dist/xy-motion/xy-motion-driver.d.ts +19 -0
- package/dist/xy-motion/xy-motion-driver.js +130 -0
- package/dist/y-axis.d.ts +7 -2
- package/dist/y-axis.js +99 -10
- package/docs/components.md +50 -1
- package/docs/getting-started.md +35 -0
- package/docs/theming.md +14 -0
- package/docs/xy-chart.md +88 -7
- package/package.json +5 -4
package/dist/base-chart.d.ts
CHANGED
|
@@ -105,6 +105,7 @@ export declare abstract class BaseChart {
|
|
|
105
105
|
*/
|
|
106
106
|
private performRender;
|
|
107
107
|
protected resolveRenderDimensions(containerRect: DOMRect): RenderDimensions;
|
|
108
|
+
private pushIfIncluded;
|
|
108
109
|
private resolveAccessibleLabel;
|
|
109
110
|
private syncAccessibleLabelFromSvg;
|
|
110
111
|
protected resolveResponsiveContext(context: {
|
|
@@ -208,6 +209,10 @@ export declare abstract class BaseChart {
|
|
|
208
209
|
private exportImage;
|
|
209
210
|
private exportPDF;
|
|
210
211
|
protected exportSVG(options?: ExportOptions, formatForHooks?: VisualExportFormat): Promise<string>;
|
|
212
|
+
private requireRenderedSvg;
|
|
213
|
+
private resolveExportContext;
|
|
214
|
+
private createExportSvgClone;
|
|
215
|
+
private populateExportChart;
|
|
211
216
|
protected exportJSON(): string;
|
|
212
217
|
}
|
|
213
218
|
export {};
|
package/dist/base-chart.js
CHANGED
|
@@ -340,22 +340,34 @@ export class BaseChart {
|
|
|
340
340
|
}
|
|
341
341
|
}
|
|
342
342
|
resolveRenderDimensions(containerRect) {
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
(containerRect.height || DEFAULT_CHART_HEIGHT);
|
|
349
|
-
return {
|
|
350
|
-
width,
|
|
351
|
-
height,
|
|
352
|
-
svgWidthAttr: this.renderSizeOverride?.width ??
|
|
353
|
-
this.configuredWidth ??
|
|
354
|
-
'100%',
|
|
355
|
-
svgHeightAttr: this.renderSizeOverride?.height ??
|
|
356
|
-
this.configuredHeight ??
|
|
357
|
-
'100%',
|
|
343
|
+
const dimensions = {
|
|
344
|
+
width: containerRect.width || DEFAULT_CHART_WIDTH,
|
|
345
|
+
height: containerRect.height || DEFAULT_CHART_HEIGHT,
|
|
346
|
+
svgWidthAttr: '100%',
|
|
347
|
+
svgHeightAttr: '100%',
|
|
358
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
|
+
}
|
|
359
371
|
}
|
|
360
372
|
resolveAccessibleLabel() {
|
|
361
373
|
const titleText = this.title?.text.trim();
|
|
@@ -511,26 +523,12 @@ export class BaseChart {
|
|
|
511
523
|
}
|
|
512
524
|
getBaseExportComponents(options) {
|
|
513
525
|
const components = [];
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
if (options.xAxis && this.xAxis) {
|
|
521
|
-
components.push(this.xAxis);
|
|
522
|
-
}
|
|
523
|
-
if (options.yAxis && this.yAxis) {
|
|
524
|
-
components.push(this.yAxis);
|
|
525
|
-
}
|
|
526
|
-
if (options.tooltip && this.tooltip) {
|
|
527
|
-
components.push(this.tooltip);
|
|
528
|
-
}
|
|
529
|
-
if (options.legend &&
|
|
530
|
-
this.legend &&
|
|
531
|
-
this.shouldIncludeLegendInExport()) {
|
|
532
|
-
components.push(this.legend);
|
|
533
|
-
}
|
|
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);
|
|
534
532
|
return components;
|
|
535
533
|
}
|
|
536
534
|
registerBaseComponent(component) {
|
|
@@ -739,7 +737,17 @@ export class BaseChart {
|
|
|
739
737
|
if (this.resizeObserver) {
|
|
740
738
|
this.resizeObserver.disconnect();
|
|
741
739
|
}
|
|
742
|
-
this.resizeObserver = new ResizeObserver(() =>
|
|
740
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
741
|
+
if (!this.container) {
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
const nextDimensions = this.resolveRenderDimensions(this.container.getBoundingClientRect());
|
|
745
|
+
if (nextDimensions.width === this.width &&
|
|
746
|
+
nextDimensions.height === this.height) {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
this.rerender();
|
|
750
|
+
});
|
|
743
751
|
this.resizeObserver.observe(this.container);
|
|
744
752
|
}
|
|
745
753
|
setReadyPromise(promise) {
|
|
@@ -1110,27 +1118,10 @@ export class BaseChart {
|
|
|
1110
1118
|
});
|
|
1111
1119
|
}
|
|
1112
1120
|
async exportSVG(options, formatForHooks = 'svg') {
|
|
1113
|
-
|
|
1114
|
-
throw new Error('Chart must be rendered before export');
|
|
1115
|
-
}
|
|
1121
|
+
const liveSvg = this.requireRenderedSvg();
|
|
1116
1122
|
await this.whenReady();
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1119
|
-
throw new Error('Chart must remain mounted until export completes');
|
|
1120
|
-
}
|
|
1121
|
-
const exportWidth = options?.width ?? this.width;
|
|
1122
|
-
const exportHeight = options?.height ?? this.height;
|
|
1123
|
-
const requiresExportRender = exportWidth !== this.width || exportHeight !== this.height;
|
|
1124
|
-
const clone = liveSvg.cloneNode(true);
|
|
1125
|
-
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
1126
|
-
clone.setAttribute('width', String(exportWidth));
|
|
1127
|
-
clone.setAttribute('height', String(exportHeight));
|
|
1128
|
-
const baseContext = {
|
|
1129
|
-
format: formatForHooks,
|
|
1130
|
-
options,
|
|
1131
|
-
width: exportWidth,
|
|
1132
|
-
height: exportHeight,
|
|
1133
|
-
};
|
|
1123
|
+
const { exportWidth, exportHeight, requiresExportRender, baseContext } = this.resolveExportContext(options, formatForHooks);
|
|
1124
|
+
const clone = this.createExportSvgClone(liveSvg, exportWidth, exportHeight);
|
|
1134
1125
|
const overrides = this.collectExportOverrides(baseContext);
|
|
1135
1126
|
if (overrides.size === 0 && !requiresExportRender) {
|
|
1136
1127
|
this.runExportHooks({
|
|
@@ -1145,17 +1136,7 @@ export class BaseChart {
|
|
|
1145
1136
|
width: exportWidth,
|
|
1146
1137
|
height: exportHeight,
|
|
1147
1138
|
};
|
|
1148
|
-
|
|
1149
|
-
components.forEach((component) => {
|
|
1150
|
-
const exportable = component;
|
|
1151
|
-
const override = overrides.get(component);
|
|
1152
|
-
if (exportable.createExportComponent) {
|
|
1153
|
-
exportChart.addChild(exportable.createExportComponent(override));
|
|
1154
|
-
}
|
|
1155
|
-
else {
|
|
1156
|
-
exportChart.addChild(component);
|
|
1157
|
-
}
|
|
1158
|
-
});
|
|
1139
|
+
this.populateExportChart(exportChart, overrides);
|
|
1159
1140
|
const exportSvg = await this.renderExportChart(exportChart, exportWidth, exportHeight);
|
|
1160
1141
|
exportSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
1161
1142
|
exportSvg.setAttribute('width', String(exportWidth));
|
|
@@ -1167,6 +1148,49 @@ export class BaseChart {
|
|
|
1167
1148
|
this.syncAccessibleLabelFromSvg(exportSvg);
|
|
1168
1149
|
return exportSvg.outerHTML;
|
|
1169
1150
|
}
|
|
1151
|
+
requireRenderedSvg() {
|
|
1152
|
+
if (!this.svg) {
|
|
1153
|
+
throw new Error('Chart must be rendered before export');
|
|
1154
|
+
}
|
|
1155
|
+
const liveSvg = this.svg.node();
|
|
1156
|
+
if (!liveSvg) {
|
|
1157
|
+
throw new Error('Chart must remain mounted until export completes');
|
|
1158
|
+
}
|
|
1159
|
+
return liveSvg;
|
|
1160
|
+
}
|
|
1161
|
+
resolveExportContext(options, format) {
|
|
1162
|
+
const exportWidth = options?.width ?? this.width;
|
|
1163
|
+
const exportHeight = options?.height ?? this.height;
|
|
1164
|
+
return {
|
|
1165
|
+
exportWidth,
|
|
1166
|
+
exportHeight,
|
|
1167
|
+
requiresExportRender: exportWidth !== this.width || exportHeight !== this.height,
|
|
1168
|
+
baseContext: {
|
|
1169
|
+
format,
|
|
1170
|
+
options,
|
|
1171
|
+
width: exportWidth,
|
|
1172
|
+
height: exportHeight,
|
|
1173
|
+
},
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
createExportSvgClone(liveSvg, width, height) {
|
|
1177
|
+
const clone = liveSvg.cloneNode(true);
|
|
1178
|
+
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
1179
|
+
clone.setAttribute('width', String(width));
|
|
1180
|
+
clone.setAttribute('height', String(height));
|
|
1181
|
+
return clone;
|
|
1182
|
+
}
|
|
1183
|
+
populateExportChart(exportChart, overrides) {
|
|
1184
|
+
this.getExportComponents().forEach((component) => {
|
|
1185
|
+
const exportable = component;
|
|
1186
|
+
const override = overrides.get(component);
|
|
1187
|
+
if (exportable.createExportComponent) {
|
|
1188
|
+
exportChart.addChild(exportable.createExportComponent(override));
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
exportChart.addChild(component);
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1170
1194
|
exportJSON() {
|
|
1171
1195
|
return JSON.stringify(this.sourceData, null, 2);
|
|
1172
1196
|
}
|
package/dist/chart-group.d.ts
CHANGED
|
@@ -109,6 +109,22 @@ export declare class ChartGroup {
|
|
|
109
109
|
private renderLegendSvg;
|
|
110
110
|
private setupResizeObserver;
|
|
111
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;
|
|
112
128
|
private validateResponsiveConfig;
|
|
113
129
|
private validateItemResponsiveConfig;
|
|
114
130
|
private resolveResponsiveConfigForWidth;
|
package/dist/chart-group.js
CHANGED
|
@@ -240,25 +240,16 @@ export class ChartGroup {
|
|
|
240
240
|
});
|
|
241
241
|
}
|
|
242
242
|
addChart(chart, options) {
|
|
243
|
-
const
|
|
244
|
-
const height = normalizeOptionalItemHeight(options?.height);
|
|
245
|
-
const hidden = options?.hidden ?? false;
|
|
246
|
-
const order = normalizeFiniteNumber(options?.order ?? this.charts.length, 'ChartGroup order');
|
|
243
|
+
const resolvedOptions = this.resolveChartOptions(options);
|
|
247
244
|
this.warnOnExplicitChildWidth(chart);
|
|
248
245
|
chart.setLegendModeOverride('hidden', false);
|
|
249
246
|
this.validateItemResponsiveConfig(options?.responsive);
|
|
250
247
|
this.charts.push({
|
|
251
248
|
chart,
|
|
252
|
-
|
|
253
|
-
height,
|
|
254
|
-
hidden,
|
|
255
|
-
order,
|
|
256
|
-
responsive: options?.responsive,
|
|
249
|
+
...resolvedOptions,
|
|
257
250
|
});
|
|
258
251
|
this.bindChartRenderCallback(chart);
|
|
259
|
-
|
|
260
|
-
this.refresh();
|
|
261
|
-
}
|
|
252
|
+
this.refreshIfMounted();
|
|
262
253
|
return this;
|
|
263
254
|
}
|
|
264
255
|
addChild(component) {
|
|
@@ -294,78 +285,18 @@ export class ChartGroup {
|
|
|
294
285
|
return null;
|
|
295
286
|
}
|
|
296
287
|
const container = this.container;
|
|
297
|
-
const
|
|
298
|
-
const width = this.resolveContainerWidth(container);
|
|
299
|
-
const renderedTitle = this.renderTitleSvg(width);
|
|
300
|
-
const renderedLegend = this.renderLegendSvg(width);
|
|
301
|
-
const totalHeightConstraint = this.resolveTotalHeightConstraint(containerRect);
|
|
302
|
-
const chartAreaHeight = totalHeightConstraint === undefined
|
|
303
|
-
? undefined
|
|
304
|
-
: Math.max(1, totalHeightConstraint -
|
|
305
|
-
(renderedTitle?.height ?? 0) -
|
|
306
|
-
(renderedLegend?.height ?? 0));
|
|
307
|
-
const layout = this.calculateLayout(width, chartAreaHeight);
|
|
308
|
-
const totalHeight = (renderedTitle?.height ?? 0) +
|
|
309
|
-
layout.chartHeight +
|
|
310
|
-
(renderedLegend?.height ?? 0);
|
|
288
|
+
const { width, renderedTitle, renderedLegend, layout, totalHeight } = this.prepareRenderState(container);
|
|
311
289
|
this.isRendering = true;
|
|
312
290
|
try {
|
|
313
291
|
this.applyScaleSyncOverrides(width);
|
|
314
292
|
container.innerHTML = '';
|
|
315
|
-
const root =
|
|
316
|
-
root
|
|
317
|
-
root.style.width = '100%';
|
|
318
|
-
root.style.height = `${totalHeight}px`;
|
|
319
|
-
if (renderedTitle) {
|
|
320
|
-
const titleHost = document.createElement('div');
|
|
321
|
-
titleHost.className = 'chart-group__title';
|
|
322
|
-
titleHost.style.width = '100%';
|
|
323
|
-
titleHost.appendChild(renderedTitle.svg);
|
|
324
|
-
root.appendChild(titleHost);
|
|
325
|
-
}
|
|
326
|
-
const chartLayer = document.createElement('div');
|
|
327
|
-
chartLayer.className = 'chart-group__charts';
|
|
328
|
-
chartLayer.style.position = 'relative';
|
|
329
|
-
chartLayer.style.width = '100%';
|
|
330
|
-
chartLayer.style.height = `${layout.chartHeight}px`;
|
|
293
|
+
const { root, chartLayer } = this.createRenderHosts(totalHeight, layout.chartHeight);
|
|
294
|
+
this.appendRenderedSection(root, 'chart-group__title', renderedTitle);
|
|
331
295
|
root.appendChild(chartLayer);
|
|
332
296
|
container.appendChild(root);
|
|
333
|
-
layout.items
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
chartHost.style.position = 'absolute';
|
|
337
|
-
chartHost.style.left = `${item.x}px`;
|
|
338
|
-
chartHost.style.top = `${item.y}px`;
|
|
339
|
-
chartHost.style.width = `${item.width}px`;
|
|
340
|
-
chartHost.style.height = `${item.height}px`;
|
|
341
|
-
Object.defineProperty(chartHost, 'getBoundingClientRect', {
|
|
342
|
-
configurable: true,
|
|
343
|
-
value: () => {
|
|
344
|
-
return {
|
|
345
|
-
x: item.x,
|
|
346
|
-
y: item.y,
|
|
347
|
-
top: item.y,
|
|
348
|
-
left: item.x,
|
|
349
|
-
right: item.x + item.width,
|
|
350
|
-
bottom: item.y + item.height,
|
|
351
|
-
width: item.width,
|
|
352
|
-
height: item.height,
|
|
353
|
-
toJSON: () => ({}),
|
|
354
|
-
};
|
|
355
|
-
},
|
|
356
|
-
});
|
|
357
|
-
chartLayer.appendChild(chartHost);
|
|
358
|
-
item.chart.setLegendModeOverride('hidden', false);
|
|
359
|
-
item.chart.render(chartHost);
|
|
360
|
-
});
|
|
361
|
-
if (renderedLegend) {
|
|
362
|
-
const legendHost = document.createElement('div');
|
|
363
|
-
legendHost.className = 'chart-group__legend';
|
|
364
|
-
legendHost.style.width = '100%';
|
|
365
|
-
legendHost.appendChild(renderedLegend.svg);
|
|
366
|
-
root.appendChild(legendHost);
|
|
367
|
-
}
|
|
368
|
-
this.readyPromise = Promise.all(this.charts.map(({ chart }) => chart.whenReady())).then(() => undefined);
|
|
297
|
+
this.renderLayoutItems(chartLayer, layout.items);
|
|
298
|
+
this.appendRenderedSection(root, 'chart-group__legend', renderedLegend);
|
|
299
|
+
this.readyPromise = this.createReadyPromise();
|
|
369
300
|
this.syncLegendStateFromChildren();
|
|
370
301
|
this.childYDomainSnapshot = this.serializeChildYDomains(width);
|
|
371
302
|
}
|
|
@@ -408,43 +339,11 @@ export class ChartGroup {
|
|
|
408
339
|
return this.legendState.subscribe(callback);
|
|
409
340
|
}
|
|
410
341
|
async export(format, options) {
|
|
411
|
-
|
|
412
|
-
throw new Error('ChartGroup must be rendered before export()');
|
|
413
|
-
}
|
|
414
|
-
if (options?.height !== undefined) {
|
|
415
|
-
throw new Error('ChartGroup export height is layout-derived and cannot be overridden in v1');
|
|
416
|
-
}
|
|
342
|
+
const container = this.requireRenderedContainer(options);
|
|
417
343
|
await this.whenReady();
|
|
418
|
-
const width = options?.width ?? this.resolveContainerWidth(
|
|
344
|
+
const width = options?.width ?? this.resolveContainerWidth(container);
|
|
419
345
|
const { svg, height } = await this.exportSVG(width, options);
|
|
420
|
-
|
|
421
|
-
if (format === 'png' || format === 'jpg') {
|
|
422
|
-
content = await exportRasterBlob({
|
|
423
|
-
format,
|
|
424
|
-
svg,
|
|
425
|
-
width,
|
|
426
|
-
height,
|
|
427
|
-
pixelRatio: options?.pixelRatio ?? 1,
|
|
428
|
-
backgroundColor: options?.backgroundColor,
|
|
429
|
-
jpegQuality: options?.jpegQuality ?? 0.92,
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
if (format === 'pdf') {
|
|
433
|
-
const pngBlob = await exportRasterBlob({
|
|
434
|
-
format: 'png',
|
|
435
|
-
svg,
|
|
436
|
-
width,
|
|
437
|
-
height,
|
|
438
|
-
pixelRatio: options?.pixelRatio ?? 1,
|
|
439
|
-
backgroundColor: options?.backgroundColor,
|
|
440
|
-
jpegQuality: options?.jpegQuality ?? 0.92,
|
|
441
|
-
});
|
|
442
|
-
content = await exportPDFBlob(pngBlob, {
|
|
443
|
-
width,
|
|
444
|
-
height,
|
|
445
|
-
margin: options?.pdfMargin ?? 0,
|
|
446
|
-
});
|
|
447
|
-
}
|
|
346
|
+
const content = await this.resolveExportContent(format, svg, width, height, options);
|
|
448
347
|
if (options?.download) {
|
|
449
348
|
this.downloadContent(content, format, options);
|
|
450
349
|
return;
|
|
@@ -878,27 +777,189 @@ export class ChartGroup {
|
|
|
878
777
|
}
|
|
879
778
|
async exportSVG(width, options) {
|
|
880
779
|
this.applyScaleSyncOverrides(width);
|
|
780
|
+
const exportLayoutState = this.resolveExportLayoutState(width);
|
|
781
|
+
const layout = this.calculateLayout(width, exportLayoutState.chartAreaHeight, exportLayoutState.defaultChartHeightOverride);
|
|
782
|
+
const childSvgs = await this.exportLayoutItems(layout.items, options);
|
|
783
|
+
const totalHeight = exportLayoutState.titleHeight +
|
|
784
|
+
layout.chartHeight +
|
|
785
|
+
exportLayoutState.legendHeight;
|
|
786
|
+
const exportSvg = this.composeExportSvg(width, totalHeight, layout.chartHeight, exportLayoutState, childSvgs);
|
|
787
|
+
return {
|
|
788
|
+
svg: exportSvg.outerHTML,
|
|
789
|
+
height: totalHeight,
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
resolveChartOptions(options) {
|
|
793
|
+
return {
|
|
794
|
+
span: normalizePositiveInteger(options?.span ?? 1, 'ChartGroup span'),
|
|
795
|
+
height: normalizeOptionalItemHeight(options?.height),
|
|
796
|
+
hidden: options?.hidden ?? false,
|
|
797
|
+
order: normalizeFiniteNumber(options?.order ?? this.charts.length, 'ChartGroup order'),
|
|
798
|
+
responsive: options?.responsive,
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
refreshIfMounted() {
|
|
802
|
+
if (this.container) {
|
|
803
|
+
this.refresh();
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
prepareRenderState(container) {
|
|
807
|
+
const width = this.resolveContainerWidth(container);
|
|
808
|
+
const renderedTitle = this.renderTitleSvg(width);
|
|
809
|
+
const renderedLegend = this.renderLegendSvg(width);
|
|
810
|
+
const chartAreaHeight = this.resolveChartAreaHeightConstraint(this.resolveTotalHeightConstraint(container.getBoundingClientRect()), renderedTitle?.height ?? 0, renderedLegend?.height ?? 0);
|
|
811
|
+
const layout = this.calculateLayout(width, chartAreaHeight);
|
|
812
|
+
const totalHeight = (renderedTitle?.height ?? 0) +
|
|
813
|
+
layout.chartHeight +
|
|
814
|
+
(renderedLegend?.height ?? 0);
|
|
815
|
+
return {
|
|
816
|
+
width,
|
|
817
|
+
renderedTitle,
|
|
818
|
+
renderedLegend,
|
|
819
|
+
layout,
|
|
820
|
+
totalHeight,
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
createRenderHosts(totalHeight, chartHeight) {
|
|
824
|
+
const root = document.createElement('div');
|
|
825
|
+
root.className = 'chart-group';
|
|
826
|
+
root.style.width = '100%';
|
|
827
|
+
root.style.height = `${totalHeight}px`;
|
|
828
|
+
const chartLayer = document.createElement('div');
|
|
829
|
+
chartLayer.className = 'chart-group__charts';
|
|
830
|
+
chartLayer.style.position = 'relative';
|
|
831
|
+
chartLayer.style.width = '100%';
|
|
832
|
+
chartLayer.style.height = `${chartHeight}px`;
|
|
833
|
+
return { root, chartLayer };
|
|
834
|
+
}
|
|
835
|
+
appendRenderedSection(root, className, section) {
|
|
836
|
+
if (!section) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const host = document.createElement('div');
|
|
840
|
+
host.className = className;
|
|
841
|
+
host.style.width = '100%';
|
|
842
|
+
host.appendChild(section.svg);
|
|
843
|
+
root.appendChild(host);
|
|
844
|
+
}
|
|
845
|
+
renderLayoutItems(chartLayer, items) {
|
|
846
|
+
items.forEach((item) => {
|
|
847
|
+
const chartHost = this.createChartHost(item);
|
|
848
|
+
chartLayer.appendChild(chartHost);
|
|
849
|
+
item.chart.setLegendModeOverride('hidden', false);
|
|
850
|
+
item.chart.render(chartHost);
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
createChartHost(item) {
|
|
854
|
+
const chartHost = document.createElement('div');
|
|
855
|
+
chartHost.className = 'chart-group__item';
|
|
856
|
+
chartHost.style.position = 'absolute';
|
|
857
|
+
chartHost.style.left = `${item.x}px`;
|
|
858
|
+
chartHost.style.top = `${item.y}px`;
|
|
859
|
+
chartHost.style.width = `${item.width}px`;
|
|
860
|
+
chartHost.style.height = `${item.height}px`;
|
|
861
|
+
Object.defineProperty(chartHost, 'getBoundingClientRect', {
|
|
862
|
+
configurable: true,
|
|
863
|
+
value: () => {
|
|
864
|
+
return {
|
|
865
|
+
x: item.x,
|
|
866
|
+
y: item.y,
|
|
867
|
+
top: item.y,
|
|
868
|
+
left: item.x,
|
|
869
|
+
right: item.x + item.width,
|
|
870
|
+
bottom: item.y + item.height,
|
|
871
|
+
width: item.width,
|
|
872
|
+
height: item.height,
|
|
873
|
+
toJSON: () => ({}),
|
|
874
|
+
};
|
|
875
|
+
},
|
|
876
|
+
});
|
|
877
|
+
return chartHost;
|
|
878
|
+
}
|
|
879
|
+
createReadyPromise() {
|
|
880
|
+
return Promise.all(this.charts.map(({ chart }) => chart.whenReady())).then(() => undefined);
|
|
881
|
+
}
|
|
882
|
+
requireRenderedContainer(options) {
|
|
883
|
+
if (!this.container) {
|
|
884
|
+
throw new Error('ChartGroup must be rendered before export()');
|
|
885
|
+
}
|
|
886
|
+
if (options?.height !== undefined) {
|
|
887
|
+
throw new Error('ChartGroup export height is layout-derived and cannot be overridden in v1');
|
|
888
|
+
}
|
|
889
|
+
return this.container;
|
|
890
|
+
}
|
|
891
|
+
async resolveExportContent(format, svg, width, height, options) {
|
|
892
|
+
if (format === 'svg') {
|
|
893
|
+
return svg;
|
|
894
|
+
}
|
|
895
|
+
const rasterOptions = this.createRasterExportOptions(svg, width, height, options);
|
|
896
|
+
if (format === 'pdf') {
|
|
897
|
+
return this.exportPdfContent(rasterOptions, width, height, options);
|
|
898
|
+
}
|
|
899
|
+
return exportRasterBlob({
|
|
900
|
+
format,
|
|
901
|
+
...rasterOptions,
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
createRasterExportOptions(svg, width, height, options) {
|
|
905
|
+
return {
|
|
906
|
+
svg,
|
|
907
|
+
width,
|
|
908
|
+
height,
|
|
909
|
+
pixelRatio: options?.pixelRatio ?? 1,
|
|
910
|
+
backgroundColor: options?.backgroundColor,
|
|
911
|
+
jpegQuality: options?.jpegQuality ?? 0.92,
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
async exportPdfContent(rasterOptions, width, height, options) {
|
|
915
|
+
const pngBlob = await exportRasterBlob({
|
|
916
|
+
format: 'png',
|
|
917
|
+
...rasterOptions,
|
|
918
|
+
});
|
|
919
|
+
return exportPDFBlob(pngBlob, {
|
|
920
|
+
width,
|
|
921
|
+
height,
|
|
922
|
+
margin: options?.pdfMargin ?? 0,
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
resolveExportLayoutState(width) {
|
|
881
926
|
const renderedTitle = this.renderTitleSvg(width);
|
|
882
927
|
const renderedLegend = this.renderLegendSvg(width);
|
|
883
928
|
const titleHeight = renderedTitle?.height ?? 0;
|
|
884
929
|
const legendHeight = renderedLegend?.height ?? 0;
|
|
885
|
-
let chartAreaHeight;
|
|
886
|
-
let defaultChartHeightOverride;
|
|
887
930
|
if (this.configuredHeight !== undefined) {
|
|
888
|
-
|
|
931
|
+
return {
|
|
932
|
+
renderedTitle,
|
|
933
|
+
renderedLegend,
|
|
934
|
+
titleHeight,
|
|
935
|
+
legendHeight,
|
|
936
|
+
chartAreaHeight: this.resolveChartAreaHeightConstraint(this.configuredHeight, titleHeight, legendHeight),
|
|
937
|
+
};
|
|
889
938
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
939
|
+
const defaultChartHeightOverride = this.resolveLiveChartHeightOverride();
|
|
940
|
+
return {
|
|
941
|
+
renderedTitle,
|
|
942
|
+
renderedLegend,
|
|
943
|
+
titleHeight,
|
|
944
|
+
legendHeight,
|
|
945
|
+
defaultChartHeightOverride,
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
resolveLiveChartHeightOverride() {
|
|
949
|
+
if (!this.container) {
|
|
950
|
+
return undefined;
|
|
951
|
+
}
|
|
952
|
+
const liveWidth = this.resolveContainerWidth(this.container);
|
|
953
|
+
const liveTitle = this.renderTitleSvg(liveWidth);
|
|
954
|
+
const liveLegend = this.renderLegendSvg(liveWidth);
|
|
955
|
+
const liveChartAreaHeight = this.resolveChartAreaHeightConstraint(this.resolveTotalHeightConstraint(this.container.getBoundingClientRect()), liveTitle?.height ?? 0, liveLegend?.height ?? 0);
|
|
956
|
+
if (liveChartAreaHeight === undefined) {
|
|
957
|
+
return undefined;
|
|
899
958
|
}
|
|
900
|
-
|
|
901
|
-
|
|
959
|
+
return this.resolveDefaultChartHeightForWidth(liveWidth, liveChartAreaHeight);
|
|
960
|
+
}
|
|
961
|
+
async exportLayoutItems(items, options) {
|
|
962
|
+
return Promise.all(items.map(async (item) => {
|
|
902
963
|
const svg = await item.chart.export('svg', {
|
|
903
964
|
...options,
|
|
904
965
|
width: item.width,
|
|
@@ -913,7 +974,8 @@ export class ChartGroup {
|
|
|
913
974
|
svg,
|
|
914
975
|
};
|
|
915
976
|
}));
|
|
916
|
-
|
|
977
|
+
}
|
|
978
|
+
composeExportSvg(width, totalHeight, chartHeight, state, childSvgs) {
|
|
917
979
|
const parser = new DOMParser();
|
|
918
980
|
const exportSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
919
981
|
exportSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
@@ -921,30 +983,26 @@ export class ChartGroup {
|
|
|
921
983
|
exportSvg.setAttribute('height', String(totalHeight));
|
|
922
984
|
exportSvg.setAttribute('role', 'img');
|
|
923
985
|
exportSvg.setAttribute('aria-label', this.title?.text.trim() || 'Chart group');
|
|
924
|
-
if (renderedTitle) {
|
|
925
|
-
renderedTitle.svg.setAttribute('x', '0');
|
|
926
|
-
renderedTitle.svg.setAttribute('y', '0');
|
|
927
|
-
exportSvg.appendChild(document.importNode(renderedTitle.svg, true));
|
|
986
|
+
if (state.renderedTitle) {
|
|
987
|
+
state.renderedTitle.svg.setAttribute('x', '0');
|
|
988
|
+
state.renderedTitle.svg.setAttribute('y', '0');
|
|
989
|
+
exportSvg.appendChild(document.importNode(state.renderedTitle.svg, true));
|
|
928
990
|
}
|
|
929
991
|
childSvgs.forEach((item) => {
|
|
930
992
|
const parsed = parser.parseFromString(item.svg, 'image/svg+xml');
|
|
931
|
-
const
|
|
932
|
-
const imported = document.importNode(childSvg, true);
|
|
993
|
+
const imported = document.importNode(parsed.documentElement, true);
|
|
933
994
|
imported.setAttribute('x', String(item.x));
|
|
934
|
-
imported.setAttribute('y', String(titleHeight + item.y));
|
|
995
|
+
imported.setAttribute('y', String(state.titleHeight + item.y));
|
|
935
996
|
imported.setAttribute('width', String(item.width));
|
|
936
997
|
imported.setAttribute('height', String(item.height));
|
|
937
998
|
exportSvg.appendChild(imported);
|
|
938
999
|
});
|
|
939
|
-
if (renderedLegend) {
|
|
940
|
-
renderedLegend.svg.setAttribute('x', '0');
|
|
941
|
-
renderedLegend.svg.setAttribute('y', String(titleHeight +
|
|
942
|
-
exportSvg.appendChild(document.importNode(renderedLegend.svg, true));
|
|
1000
|
+
if (state.renderedLegend) {
|
|
1001
|
+
state.renderedLegend.svg.setAttribute('x', '0');
|
|
1002
|
+
state.renderedLegend.svg.setAttribute('y', String(state.titleHeight + chartHeight));
|
|
1003
|
+
exportSvg.appendChild(document.importNode(state.renderedLegend.svg, true));
|
|
943
1004
|
}
|
|
944
|
-
return
|
|
945
|
-
svg: exportSvg.outerHTML,
|
|
946
|
-
height: totalHeight,
|
|
947
|
-
};
|
|
1005
|
+
return exportSvg;
|
|
948
1006
|
}
|
|
949
1007
|
validateResponsiveConfig(responsive) {
|
|
950
1008
|
const breakpoints = responsive?.breakpoints;
|
|
@@ -29,5 +29,6 @@ export declare class DonutCenterContent implements ChartComponent<DonutCenterCon
|
|
|
29
29
|
getExportConfig(): DonutCenterContentConfigBase;
|
|
30
30
|
createExportComponent(override?: Partial<DonutCenterContentConfigBase>): ChartComponent<DonutCenterContentConfigBase>;
|
|
31
31
|
render(svg: Selection<SVGSVGElement, undefined, null, undefined>, cx: number, cy: number, theme: ChartTheme, fontScale?: number): void;
|
|
32
|
+
private buildElement;
|
|
32
33
|
}
|
|
33
34
|
export {};
|