@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.
Files changed (57) hide show
  1. package/README.md +65 -1
  2. package/dist/area.d.ts +11 -1
  3. package/dist/area.js +199 -55
  4. package/dist/bar.d.ts +26 -1
  5. package/dist/bar.js +425 -306
  6. package/dist/base-chart.d.ts +5 -0
  7. package/dist/base-chart.js +91 -67
  8. package/dist/chart-group.d.ts +16 -0
  9. package/dist/chart-group.js +201 -143
  10. package/dist/donut-center-content.d.ts +1 -0
  11. package/dist/donut-center-content.js +21 -38
  12. package/dist/donut-chart.js +32 -32
  13. package/dist/gauge-chart.d.ts +23 -4
  14. package/dist/gauge-chart.js +235 -185
  15. package/dist/lazy-mount.d.ts +13 -0
  16. package/dist/lazy-mount.js +90 -0
  17. package/dist/legend.js +10 -9
  18. package/dist/line.d.ts +9 -1
  19. package/dist/line.js +144 -24
  20. package/dist/pie-chart.d.ts +3 -0
  21. package/dist/pie-chart.js +49 -47
  22. package/dist/radial-chart-base.d.ts +4 -3
  23. package/dist/radial-chart-base.js +27 -12
  24. package/dist/scatter.d.ts +5 -1
  25. package/dist/scatter.js +92 -9
  26. package/dist/theme.js +17 -0
  27. package/dist/tooltip.d.ts +55 -3
  28. package/dist/tooltip.js +968 -159
  29. package/dist/types.d.ts +23 -1
  30. package/dist/utils.js +11 -19
  31. package/dist/x-axis.d.ts +10 -0
  32. package/dist/x-axis.js +190 -149
  33. package/dist/xy-animation.d.ts +3 -0
  34. package/dist/xy-animation.js +2 -0
  35. package/dist/xy-chart.d.ts +35 -1
  36. package/dist/xy-chart.js +358 -153
  37. package/dist/xy-motion/config.d.ts +2 -0
  38. package/dist/xy-motion/config.js +177 -0
  39. package/dist/xy-motion/driver.d.ts +9 -0
  40. package/dist/xy-motion/driver.js +10 -0
  41. package/dist/xy-motion/helpers.d.ts +17 -0
  42. package/dist/xy-motion/helpers.js +105 -0
  43. package/dist/xy-motion/live-state.d.ts +8 -0
  44. package/dist/xy-motion/live-state.js +240 -0
  45. package/dist/xy-motion/noop-xy-motion-driver.d.ts +9 -0
  46. package/dist/xy-motion/noop-xy-motion-driver.js +15 -0
  47. package/dist/xy-motion/types.d.ts +85 -0
  48. package/dist/xy-motion/types.js +1 -0
  49. package/dist/xy-motion/xy-motion-driver.d.ts +19 -0
  50. package/dist/xy-motion/xy-motion-driver.js +130 -0
  51. package/dist/y-axis.d.ts +7 -2
  52. package/dist/y-axis.js +99 -10
  53. package/docs/components.md +50 -1
  54. package/docs/getting-started.md +35 -0
  55. package/docs/theming.md +14 -0
  56. package/docs/xy-chart.md +88 -7
  57. package/package.json +5 -4
@@ -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 {};
@@ -340,22 +340,34 @@ export class BaseChart {
340
340
  }
341
341
  }
342
342
  resolveRenderDimensions(containerRect) {
343
- const width = this.renderSizeOverride?.width ??
344
- this.configuredWidth ??
345
- (containerRect.width || DEFAULT_CHART_WIDTH);
346
- const height = this.renderSizeOverride?.height ??
347
- this.configuredHeight ??
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
- if (options.title && this.title) {
515
- components.push(this.title);
516
- }
517
- if (options.grid && this.grid) {
518
- components.push(this.grid);
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(() => this.rerender());
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
- if (!this.svg) {
1114
- throw new Error('Chart must be rendered before export');
1115
- }
1121
+ const liveSvg = this.requireRenderedSvg();
1116
1122
  await this.whenReady();
1117
- const liveSvg = this.svg?.node();
1118
- if (!liveSvg) {
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
- const components = this.getExportComponents();
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
  }
@@ -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;
@@ -240,25 +240,16 @@ export class ChartGroup {
240
240
  });
241
241
  }
242
242
  addChart(chart, options) {
243
- const span = normalizePositiveInteger(options?.span ?? 1, 'ChartGroup span');
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
- span,
253
- height,
254
- hidden,
255
- order,
256
- responsive: options?.responsive,
249
+ ...resolvedOptions,
257
250
  });
258
251
  this.bindChartRenderCallback(chart);
259
- if (this.container) {
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 containerRect = container.getBoundingClientRect();
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 = document.createElement('div');
316
- root.className = 'chart-group';
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.forEach((item) => {
334
- const chartHost = document.createElement('div');
335
- chartHost.className = 'chart-group__item';
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
- if (!this.container) {
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(this.container);
344
+ const width = options?.width ?? this.resolveContainerWidth(container);
419
345
  const { svg, height } = await this.exportSVG(width, options);
420
- let content = svg;
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
- chartAreaHeight = this.resolveChartAreaHeightConstraint(this.configuredHeight, titleHeight, legendHeight);
931
+ return {
932
+ renderedTitle,
933
+ renderedLegend,
934
+ titleHeight,
935
+ legendHeight,
936
+ chartAreaHeight: this.resolveChartAreaHeightConstraint(this.configuredHeight, titleHeight, legendHeight),
937
+ };
889
938
  }
890
- else if (this.container) {
891
- const liveWidth = this.resolveContainerWidth(this.container);
892
- const liveTitle = this.renderTitleSvg(liveWidth);
893
- const liveLegend = this.renderLegendSvg(liveWidth);
894
- const liveChartAreaHeight = this.resolveChartAreaHeightConstraint(this.resolveTotalHeightConstraint(this.container.getBoundingClientRect()), liveTitle?.height ?? 0, liveLegend?.height ?? 0);
895
- if (liveChartAreaHeight !== undefined) {
896
- defaultChartHeightOverride =
897
- this.resolveDefaultChartHeightForWidth(liveWidth, liveChartAreaHeight);
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
- const layout = this.calculateLayout(width, chartAreaHeight, defaultChartHeightOverride);
901
- const childSvgs = await Promise.all(layout.items.map(async (item) => {
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
- const totalHeight = titleHeight + layout.chartHeight + legendHeight;
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 childSvg = parsed.documentElement;
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 + layout.chartHeight));
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 {};