@internetstiftelsen/charts 0.11.0 → 0.13.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 CHANGED
@@ -139,6 +139,19 @@ await chart.whenReady();
139
139
  Animation is off by default, applies to XY series marks only, and visual
140
140
  exports always render the final static state.
141
141
 
142
+ ## Lifecycle Events
143
+
144
+ Charts expose `on()` and `off()` for lifecycle subscriptions.
145
+
146
+ ```typescript
147
+ chart.on('ready', (event) => {
148
+ console.log(event.reason);
149
+ });
150
+ ```
151
+
152
+ See [Getting Started](./docs/getting-started.md#lifecycle-events) for the
153
+ supported events and payloads.
154
+
142
155
  ## Lazy Loading
143
156
 
144
157
  Use `mountChartWhenVisible` when you want the page to wait until a chart is
@@ -185,6 +198,7 @@ import { XYChart } from '@internetstiftelsen/charts/xy-chart';
185
198
  import { Line } from '@internetstiftelsen/charts/line';
186
199
  import { Bar } from '@internetstiftelsen/charts/bar';
187
200
  import { Legend } from '@internetstiftelsen/charts/legend';
201
+ import { Text } from '@internetstiftelsen/charts/text';
188
202
  import { Title } from '@internetstiftelsen/charts/title';
189
203
 
190
204
  const lineChart = new XYChart({ data: lineData });
@@ -202,6 +216,14 @@ const group = new ChartGroup({
202
216
 
203
217
  group
204
218
  .addChild(new Title({ text: 'Revenue vs Expenses' }))
219
+ .addChild(
220
+ new Text({
221
+ text: 'Source: finance team',
222
+ position: 'bottom',
223
+ variant: 'caption',
224
+ align: 'left',
225
+ }),
226
+ )
205
227
  .addChart(barChart)
206
228
  .addChart(lineChart)
207
229
  .addChild(new Legend());
@@ -413,7 +435,7 @@ Grouped parsing rules:
413
435
  - [DonutChart](./docs/donut-chart.md) - Donut/pie charts API
414
436
  - [PieChart](./docs/pie-chart.md) - Pie chart API
415
437
  - [GaugeChart](./docs/gauge-chart.md) - Gauge chart API
416
- - [Components](./docs/components.md) - Axes, Grid, Tooltip, Legend, Title
438
+ - [Components](./docs/components.md) - Axes, Grid, Tooltip, Legend, Text, Title
417
439
  - [Theming](./docs/theming.md) - Colors, fonts, and styling
418
440
 
419
441
  ## Browser Support
@@ -1,5 +1,5 @@
1
1
  import { type Selection } from 'd3';
2
- import type { ChartData, DataItem, ChartTheme, ResolvedChartTheme, AxisScaleConfig, ExportFormat, ExportOptions, D3Scale, ExportHookContext, ExportRenderContext, LegendItem, LegendMode, LegendSeries, ResponsiveConfig, ResponsiveRenderContext, DeepPartial } from './types.js';
2
+ import type { ChartData, DataItem, ChartTheme, ResolvedChartTheme, AxisScaleConfig, ExportFormat, ExportOptions, D3Scale, ExportHookContext, ExportRenderContext, LegendItem, LegendMode, LegendSeries, ResponsiveConfig, ResponsiveRenderContext, DeepPartial, TextPosition } from './types.js';
3
3
  import type { ChartComponentBase, LayoutAwareComponentBase } from './chart-interface.js';
4
4
  import type { XAxis } from './x-axis.js';
5
5
  import type { YAxis } from './y-axis.js';
@@ -34,6 +34,33 @@ type BaseExportComponentsOptions = {
34
34
  tooltip?: boolean;
35
35
  legend?: boolean;
36
36
  };
37
+ export type ChartRenderReason = 'initial' | 'manual' | 'update' | 'resize' | 'legend' | 'component';
38
+ export type ChartRenderEventName = 'render:start' | 'render' | 'ready';
39
+ type ChartEventBase<TEventName extends string> = {
40
+ type: TEventName;
41
+ chart: BaseChart;
42
+ };
43
+ export type ChartRenderEvent<TEventName extends ChartRenderEventName> = ChartEventBase<TEventName> & {
44
+ reason: ChartRenderReason;
45
+ };
46
+ export type ChartDataEvent = ChartEventBase<'data'> & {
47
+ data: ChartData;
48
+ previousData: ChartData;
49
+ };
50
+ export type ChartLegendChangeEvent = ChartEventBase<'legend:change'>;
51
+ export type ChartDestroyEvent = ChartEventBase<'destroy'>;
52
+ export type ChartEventMap = {
53
+ 'render:start': ChartRenderEvent<'render:start'>;
54
+ render: ChartRenderEvent<'render'>;
55
+ ready: ChartRenderEvent<'ready'>;
56
+ data: ChartDataEvent;
57
+ 'legend:change': ChartLegendChangeEvent;
58
+ destroy: ChartDestroyEvent;
59
+ };
60
+ export type ChartEventName = keyof ChartEventMap;
61
+ export type ChartEventListener<TEventName extends ChartEventName> = (event: ChartEventMap[TEventName]) => void;
62
+ type ChartEventPayload<TEventName extends ChartEventName> = Omit<ChartEventMap[TEventName], 'type' | 'chart'>;
63
+ type ChartEventPayloadArgs<TEventName extends ChartEventName> = keyof ChartEventPayload<TEventName> extends never ? [payload?: ChartEventPayload<TEventName>] : [payload: ChartEventPayload<TEventName>];
37
64
  export type BaseLayoutContext = {
38
65
  svg: Selection<SVGSVGElement, undefined, null, undefined>;
39
66
  svgNode: SVGSVGElement;
@@ -48,6 +75,13 @@ type ComponentSlot<TComponent extends ChartComponentBase = ChartComponentBase> =
48
75
  set: (component: TComponent | null) => void;
49
76
  onRegister?: (component: TComponent) => void;
50
77
  };
78
+ type TextLayoutComponent = LayoutAwareComponentBase & ChartComponentBase & {
79
+ display: boolean;
80
+ text: string;
81
+ position: TextPosition;
82
+ variant: string;
83
+ render: (svg: Selection<SVGSVGElement, undefined, null, undefined>, theme: ChartTheme, width: number, x?: number, y?: number) => void;
84
+ };
51
85
  export type BaseChartConfig = {
52
86
  data: ChartData;
53
87
  width?: number;
@@ -75,6 +109,7 @@ export declare abstract class BaseChart {
75
109
  protected tooltip: Tooltip | null;
76
110
  protected legend: Legend | null;
77
111
  protected title: Title | null;
112
+ protected textComponents: TextLayoutComponent[];
78
113
  protected svg: Selection<SVGSVGElement, undefined, null, undefined> | null;
79
114
  protected plotGroup: Selection<SVGGElement, undefined, null, undefined> | null;
80
115
  protected container: HTMLElement | null;
@@ -88,8 +123,12 @@ export declare abstract class BaseChart {
88
123
  private disconnectedLegendContainer;
89
124
  private renderThemeOverride;
90
125
  private renderSizeOverride;
91
- private readonly renderCallbacks;
92
126
  private legendModeOverride;
127
+ private readonly eventListeners;
128
+ private renderId;
129
+ private readyEventVersion;
130
+ private isPerformingRender;
131
+ private currentRenderReason;
93
132
  protected constructor(config: BaseChartConfig);
94
133
  /**
95
134
  * Adds a component (axis, grid, tooltip, etc.) to the chart
@@ -108,6 +147,7 @@ export declare abstract class BaseChart {
108
147
  private pushIfIncluded;
109
148
  private resolveAccessibleLabel;
110
149
  private syncAccessibleLabelFromSvg;
150
+ private resolvePrimaryTextLabel;
111
151
  protected resolveResponsiveContext(context: {
112
152
  width: number;
113
153
  height: number;
@@ -126,14 +166,19 @@ export declare abstract class BaseChart {
126
166
  height?: number;
127
167
  };
128
168
  setLegendModeOverride(mode: LegendMode | null, rerender?: boolean): this;
169
+ on<TEventName extends ChartEventName>(eventName: TEventName, listener: ChartEventListener<TEventName>): this;
170
+ off<TEventName extends ChartEventName>(eventName: TEventName, listener: ChartEventListener<TEventName>): this;
129
171
  onRender(callback: () => void): () => void;
172
+ protected emit<TEventName extends ChartEventName>(eventName: TEventName, ...[payload]: ChartEventPayloadArgs<TEventName>): void;
130
173
  private notifyRendered;
174
+ private createRenderEventPayload;
131
175
  /**
132
176
  * Get layout-aware components in order
133
177
  * Override in subclasses to provide chart-specific components
134
178
  */
135
179
  protected getLayoutComponents(): LayoutAwareComponentBase[];
136
180
  protected getBaseLayoutComponents(options: BaseLayoutComponentsOptions): LayoutAwareComponentBase[];
181
+ private getTextComponents;
137
182
  protected getExportComponents(): ChartComponentBase[];
138
183
  protected getOverrideableComponents(): ChartComponentBase[];
139
184
  protected getBaseExportComponents(options: BaseExportComponentsOptions): ChartComponentBase[];
@@ -160,6 +205,7 @@ export declare abstract class BaseChart {
160
205
  * Setup ResizeObserver for automatic resize handling
161
206
  */
162
207
  private setupResizeObserver;
208
+ private notifyLegendChanged;
163
209
  /**
164
210
  * Subclasses must implement this method to define their rendering logic
165
211
  */
@@ -186,10 +232,11 @@ export declare abstract class BaseChart {
186
232
  private resolveDisconnectedLegendHost;
187
233
  private cleanupDisconnectedLegendContainer;
188
234
  protected parseValue(value: unknown): number;
189
- protected rerender(): void;
235
+ protected rerender(reason?: ChartRenderReason): void;
190
236
  protected tryRegisterComponent(component: ChartComponentBase, slots: readonly ComponentSlot[]): boolean;
191
237
  protected applySlotOverrides(overrides: Map<ChartComponentBase, ChartComponentBase>, slots: readonly ComponentSlot[]): () => void;
192
238
  protected applyArrayComponentOverrides<TComponent extends ChartComponentBase>(components: TComponent[], overrides: Map<ChartComponentBase, ChartComponentBase>, isComponent: (component: ChartComponentBase) => component is TComponent): () => void;
239
+ private isTextComponent;
193
240
  private getBaseComponentSlots;
194
241
  /**
195
242
  * Exports the chart in the specified format
@@ -135,6 +135,12 @@ export class BaseChart {
135
135
  writable: true,
136
136
  value: null
137
137
  });
138
+ Object.defineProperty(this, "textComponents", {
139
+ enumerable: true,
140
+ configurable: true,
141
+ writable: true,
142
+ value: []
143
+ });
138
144
  Object.defineProperty(this, "svg", {
139
145
  enumerable: true,
140
146
  configurable: true,
@@ -213,17 +219,41 @@ export class BaseChart {
213
219
  writable: true,
214
220
  value: null
215
221
  });
216
- Object.defineProperty(this, "renderCallbacks", {
222
+ Object.defineProperty(this, "legendModeOverride", {
217
223
  enumerable: true,
218
224
  configurable: true,
219
225
  writable: true,
220
- value: new Set()
226
+ value: null
221
227
  });
222
- Object.defineProperty(this, "legendModeOverride", {
228
+ Object.defineProperty(this, "eventListeners", {
223
229
  enumerable: true,
224
230
  configurable: true,
225
231
  writable: true,
226
- value: null
232
+ value: new Map()
233
+ });
234
+ Object.defineProperty(this, "renderId", {
235
+ enumerable: true,
236
+ configurable: true,
237
+ writable: true,
238
+ value: 0
239
+ });
240
+ Object.defineProperty(this, "readyEventVersion", {
241
+ enumerable: true,
242
+ configurable: true,
243
+ writable: true,
244
+ value: 0
245
+ });
246
+ Object.defineProperty(this, "isPerformingRender", {
247
+ enumerable: true,
248
+ configurable: true,
249
+ writable: true,
250
+ value: false
251
+ });
252
+ Object.defineProperty(this, "currentRenderReason", {
253
+ enumerable: true,
254
+ configurable: true,
255
+ writable: true,
256
+ value: 'manual'
227
257
  });
228
258
  const normalized = normalizeChartData(config.data);
229
259
  ChartValidator.validateData(normalized.data);
@@ -238,7 +268,8 @@ export class BaseChart {
238
268
  this.responsiveConfig = config.responsive;
239
269
  this.legendState = new LegendStateController();
240
270
  this.legendState.subscribe(() => {
241
- this.rerender();
271
+ this.notifyLegendChanged();
272
+ this.rerender('legend');
242
273
  });
243
274
  this.layoutManager = new LayoutManager(this.resolvedRenderTheme);
244
275
  }
@@ -253,12 +284,13 @@ export class BaseChart {
253
284
  * Renders the chart to the specified target element
254
285
  */
255
286
  render(target) {
287
+ const reason = this.container ? 'manual' : 'initial';
256
288
  const container = this.resolveContainer(target);
257
289
  this.cleanupDisconnectedLegendContainer();
258
290
  this.container = container;
259
291
  container.innerHTML = '';
260
292
  // Perform initial render
261
- this.performRender();
293
+ this.performRender(reason);
262
294
  // Set up ResizeObserver for automatic resize handling
263
295
  this.setupResizeObserver();
264
296
  return container;
@@ -279,11 +311,13 @@ export class BaseChart {
279
311
  /**
280
312
  * Performs the actual rendering logic
281
313
  */
282
- performRender() {
314
+ performRender(reason) {
283
315
  if (!this.container) {
284
316
  return;
285
317
  }
286
- const dimensions = this.resolveRenderDimensions(this.container.getBoundingClientRect());
318
+ const container = this.container;
319
+ this.renderId += 1;
320
+ const dimensions = this.resolveRenderDimensions(container.getBoundingClientRect());
287
321
  this.width = dimensions.width;
288
322
  this.height = dimensions.height;
289
323
  const sizeContext = {
@@ -297,17 +331,24 @@ export class BaseChart {
297
331
  const overrideComponents = this.createOverrideComponents(mergedComponentOverrides);
298
332
  const restoreComponents = this.applyComponentOverrides(overrideComponents);
299
333
  const restoreTheme = this.applyRenderTheme(renderTheme);
334
+ let renderCompleted = false;
300
335
  try {
336
+ this.currentRenderReason = reason;
337
+ this.isPerformingRender = true;
301
338
  this.setReadyPromise(Promise.resolve());
339
+ this.svg = null;
340
+ this.plotGroup = null;
341
+ this.plotArea = null;
342
+ this.emit('render:start', this.createRenderEventPayload(reason));
302
343
  // Clear and setup SVG
303
- this.container.innerHTML = '';
344
+ container.innerHTML = '';
304
345
  this.svg = create('svg')
305
346
  .attr('width', dimensions.svgWidthAttr)
306
347
  .attr('height', dimensions.svgHeightAttr)
307
348
  .attr('role', 'img')
308
349
  .attr('aria-label', this.resolveAccessibleLabel())
309
350
  .style('display', 'block');
310
- this.container.appendChild(this.svg.node());
351
+ container.appendChild(this.svg.node());
311
352
  const svgNode = this.svg.node();
312
353
  if (!svgNode) {
313
354
  throw new Error('Failed to initialize chart SVG');
@@ -332,9 +373,14 @@ export class BaseChart {
332
373
  plotArea,
333
374
  });
334
375
  this.renderDisconnectedLegend();
335
- this.notifyRendered();
376
+ this.notifyRendered(reason);
377
+ renderCompleted = true;
336
378
  }
337
379
  finally {
380
+ if (!renderCompleted) {
381
+ this.readyEventVersion += 1;
382
+ }
383
+ this.isPerformingRender = false;
338
384
  restoreComponents();
339
385
  restoreTheme();
340
386
  }
@@ -370,20 +416,31 @@ export class BaseChart {
370
416
  }
371
417
  }
372
418
  resolveAccessibleLabel() {
373
- const titleText = this.title?.text.trim();
419
+ const titleText = this.resolvePrimaryTextLabel();
374
420
  if (titleText) {
375
421
  return titleText;
376
422
  }
377
423
  return 'Chart';
378
424
  }
379
425
  syncAccessibleLabelFromSvg(svg) {
380
- const titleText = svg.querySelector('.title text')?.textContent?.trim();
426
+ const titleText = svg
427
+ .querySelector('.title text, .text--title text')
428
+ ?.textContent?.trim();
381
429
  if (titleText) {
382
430
  svg.setAttribute('aria-label', titleText);
383
431
  return;
384
432
  }
385
433
  svg.setAttribute('aria-label', this.resolveAccessibleLabel());
386
434
  }
435
+ resolvePrimaryTextLabel() {
436
+ return this.textComponents
437
+ .find((component) => {
438
+ return (component.display &&
439
+ (component.type === 'title' ||
440
+ component.variant === 'title'));
441
+ })
442
+ ?.text.trim();
443
+ }
387
444
  resolveResponsiveContext(context) {
388
445
  const activeBreakpoints = this.resolveActiveBreakpoints(context.width).map(({ name }) => name);
389
446
  return {
@@ -465,21 +522,61 @@ export class BaseChart {
465
522
  }
466
523
  this.legendModeOverride = mode;
467
524
  if (rerender) {
468
- this.rerender();
525
+ this.rerender('component');
526
+ }
527
+ return this;
528
+ }
529
+ on(eventName, listener) {
530
+ let listeners = this.eventListeners.get(eventName);
531
+ if (!listeners) {
532
+ listeners = new Set();
533
+ this.eventListeners.set(eventName, listeners);
534
+ }
535
+ listeners.add(listener);
536
+ return this;
537
+ }
538
+ off(eventName, listener) {
539
+ const listeners = this.eventListeners.get(eventName);
540
+ if (!listeners) {
541
+ return this;
542
+ }
543
+ listeners.delete(listener);
544
+ if (listeners.size === 0) {
545
+ this.eventListeners.delete(eventName);
469
546
  }
470
547
  return this;
471
548
  }
472
549
  onRender(callback) {
473
- this.renderCallbacks.add(callback);
550
+ const listener = () => {
551
+ callback();
552
+ };
553
+ this.on('render', listener);
474
554
  return () => {
475
- this.renderCallbacks.delete(callback);
555
+ this.off('render', listener);
476
556
  };
477
557
  }
478
- notifyRendered() {
479
- this.renderCallbacks.forEach((callback) => {
480
- callback();
558
+ emit(eventName, ...[payload]) {
559
+ const listeners = this.eventListeners.get(eventName);
560
+ if (!listeners) {
561
+ return;
562
+ }
563
+ const event = {
564
+ ...(payload ?? {}),
565
+ type: eventName,
566
+ chart: this,
567
+ };
568
+ [...listeners].forEach((listener) => {
569
+ listener(event);
481
570
  });
482
571
  }
572
+ notifyRendered(reason) {
573
+ this.emit('render', this.createRenderEventPayload(reason));
574
+ }
575
+ createRenderEventPayload(reason) {
576
+ return {
577
+ reason,
578
+ };
579
+ }
483
580
  /**
484
581
  * Get layout-aware components in order
485
582
  * Override in subclasses to provide chart-specific components
@@ -494,8 +591,8 @@ export class BaseChart {
494
591
  }
495
592
  getBaseLayoutComponents(options) {
496
593
  const components = [];
497
- if (options.title && this.title) {
498
- components.push(this.title);
594
+ if (options.title) {
595
+ components.push(...this.getTextComponents('top'));
499
596
  }
500
597
  if (options.xAxis && this.xAxis) {
501
598
  components.push(this.xAxis);
@@ -506,8 +603,16 @@ export class BaseChart {
506
603
  if (options.inlineLegend && this.legend && this.hasInlineLegend()) {
507
604
  components.push(this.legend);
508
605
  }
606
+ if (options.title) {
607
+ components.push(...this.getTextComponents('bottom'));
608
+ }
509
609
  return components;
510
610
  }
611
+ getTextComponents(position) {
612
+ return this.textComponents.filter((component) => {
613
+ return component.position === position;
614
+ });
615
+ }
511
616
  getExportComponents() {
512
617
  return this.getBaseExportComponents({
513
618
  title: true,
@@ -523,7 +628,9 @@ export class BaseChart {
523
628
  }
524
629
  getBaseExportComponents(options) {
525
630
  const components = [];
526
- this.pushIfIncluded(components, options.title, this.title);
631
+ if (options.title) {
632
+ components.push(...this.textComponents);
633
+ }
527
634
  this.pushIfIncluded(components, options.grid, this.grid);
528
635
  this.pushIfIncluded(components, options.xAxis, this.xAxis);
529
636
  this.pushIfIncluded(components, options.yAxis, this.yAxis);
@@ -532,6 +639,13 @@ export class BaseChart {
532
639
  return components;
533
640
  }
534
641
  registerBaseComponent(component) {
642
+ if (this.isTextComponent(component)) {
643
+ this.textComponents.push(component);
644
+ if (component.type === 'title' && !this.title) {
645
+ this.title = component;
646
+ }
647
+ return true;
648
+ }
535
649
  return this.tryRegisterComponent(component, this.getBaseComponentSlots());
536
650
  }
537
651
  collectExportOverrides(context) {
@@ -659,7 +773,18 @@ export class BaseChart {
659
773
  return overrideComponents;
660
774
  }
661
775
  applyComponentOverrides(overrides) {
662
- return this.applySlotOverrides(overrides, this.getBaseComponentSlots());
776
+ const previousTitle = this.title;
777
+ const restoreSlots = this.applySlotOverrides(overrides, this.getBaseComponentSlots());
778
+ const restoreText = this.applyArrayComponentOverrides(this.textComponents, overrides, this.isTextComponent);
779
+ this.title =
780
+ this.textComponents.find((component) => {
781
+ return component.type === 'title';
782
+ }) ?? null;
783
+ return () => {
784
+ restoreText();
785
+ restoreSlots();
786
+ this.title = previousTitle;
787
+ };
663
788
  }
664
789
  renderExportChart(chart, width, height) {
665
790
  const container = document.createElement('div');
@@ -690,11 +815,10 @@ export class BaseChart {
690
815
  });
691
816
  }
692
817
  renderTitle(svg) {
693
- if (!this.title) {
694
- return;
695
- }
696
- const position = this.layoutManager.getComponentPosition(this.title);
697
- this.title.render(svg, this.renderTheme, this.width, position.x, position.y);
818
+ this.textComponents.forEach((component) => {
819
+ const position = this.layoutManager.getComponentPosition(component);
820
+ component.render(svg, this.renderTheme, this.width, position.x, position.y);
821
+ });
698
822
  }
699
823
  renderInlineLegend(svg) {
700
824
  if (!this.legend || !this.hasInlineLegend()) {
@@ -732,8 +856,9 @@ export class BaseChart {
732
856
  * Setup ResizeObserver for automatic resize handling
733
857
  */
734
858
  setupResizeObserver() {
735
- if (!this.container)
859
+ if (!this.container) {
736
860
  return;
861
+ }
737
862
  if (this.resizeObserver) {
738
863
  this.resizeObserver.disconnect();
739
864
  }
@@ -746,12 +871,32 @@ export class BaseChart {
746
871
  nextDimensions.height === this.height) {
747
872
  return;
748
873
  }
749
- this.rerender();
874
+ this.rerender('resize');
750
875
  });
751
876
  this.resizeObserver.observe(this.container);
752
877
  }
878
+ notifyLegendChanged() {
879
+ this.emit('legend:change');
880
+ }
753
881
  setReadyPromise(promise) {
754
882
  this.readyPromise = promise;
883
+ if (!this.isPerformingRender || !this.container) {
884
+ return;
885
+ }
886
+ const renderId = this.renderId;
887
+ const reason = this.currentRenderReason;
888
+ const container = this.container;
889
+ const readyVersion = ++this.readyEventVersion;
890
+ void promise
891
+ .then(() => {
892
+ if (readyVersion !== this.readyEventVersion ||
893
+ renderId !== this.renderId ||
894
+ container !== this.container) {
895
+ return;
896
+ }
897
+ this.emit('ready', this.createRenderEventPayload(reason));
898
+ })
899
+ .catch(() => undefined);
755
900
  }
756
901
  whenReady() {
757
902
  return this.readyPromise;
@@ -796,16 +941,23 @@ export class BaseChart {
796
941
  this.validateSourceData(data);
797
942
  const normalized = normalizeChartData(data);
798
943
  ChartValidator.validateData(normalized.data);
944
+ const previousSourceData = this.sourceData;
799
945
  const previousData = this.data;
800
946
  this.sourceData = data;
801
947
  this.data = normalized.data;
802
948
  this.syncDerivedState(previousData);
803
- this.rerender();
949
+ this.emit('data', {
950
+ data: this.sourceData,
951
+ previousData: previousSourceData,
952
+ });
953
+ this.rerender('update');
804
954
  }
805
955
  /**
806
956
  * Destroys the chart and cleans up resources
807
957
  */
808
958
  destroy() {
959
+ this.readyEventVersion += 1;
960
+ this.emit('destroy');
809
961
  this.tooltip?.cleanup();
810
962
  if (this.resizeObserver) {
811
963
  this.resizeObserver.disconnect();
@@ -900,11 +1052,11 @@ export class BaseChart {
900
1052
  }
901
1053
  return 0;
902
1054
  }
903
- rerender() {
1055
+ rerender(reason = 'manual') {
904
1056
  if (!this.container) {
905
1057
  return;
906
1058
  }
907
- this.performRender();
1059
+ this.performRender(reason);
908
1060
  }
909
1061
  tryRegisterComponent(component, slots) {
910
1062
  const slot = slots.find((entry) => entry.type === component.type);
@@ -952,15 +1104,11 @@ export class BaseChart {
952
1104
  components.splice(0, components.length, ...previousState);
953
1105
  };
954
1106
  }
1107
+ isTextComponent(component) {
1108
+ return component.type === 'text' || component.type === 'title';
1109
+ }
955
1110
  getBaseComponentSlots() {
956
1111
  return [
957
- {
958
- type: 'title',
959
- get: () => this.title,
960
- set: (component) => {
961
- this.title = component;
962
- },
963
- },
964
1112
  {
965
1113
  type: 'grid',
966
1114
  get: () => this.grid,
@@ -56,7 +56,7 @@ export declare class ChartGroup {
56
56
  private readonly warnedColorConflicts;
57
57
  private container;
58
58
  private legend;
59
- private title;
59
+ private readonly textComponents;
60
60
  private resizeObserver;
61
61
  private readyPromise;
62
62
  private childLegendSnapshot;
@@ -69,6 +69,7 @@ export declare class ChartGroup {
69
69
  addChild(component: {
70
70
  type: string;
71
71
  }): this;
72
+ private isTextComponent;
72
73
  render(target: string | HTMLElement): HTMLElement | null;
73
74
  refresh(): HTMLElement | null;
74
75
  whenReady(): Promise<void>;
@@ -79,6 +80,7 @@ export declare class ChartGroup {
79
80
  toggleLegendSeries(dataKey: string): this;
80
81
  setLegendVisibility(visibility: Record<string, boolean>): this;
81
82
  onLegendChange(callback: () => void): () => void;
83
+ private resolveAccessibleLabel;
82
84
  export(format: ChartGroupExportFormat, options?: ExportOptions): Promise<string | Blob | void>;
83
85
  destroy(): void;
84
86
  private bindChartRenderCallback;
@@ -105,15 +107,19 @@ export declare class ChartGroup {
105
107
  private serializeChildLegendState;
106
108
  private serializeChildYDomains;
107
109
  private renderLegendIntoContainer;
108
- private renderTitleSvg;
110
+ private renderTextSvg;
111
+ private renderTextSvgs;
109
112
  private renderLegendSvg;
110
113
  private setupResizeObserver;
111
114
  private exportSVG;
112
115
  private resolveChartOptions;
113
116
  private refreshIfMounted;
114
117
  private prepareRenderState;
118
+ private sumRenderedTextHeight;
115
119
  private createRenderHosts;
116
120
  private appendRenderedSection;
121
+ private appendRenderedTextSections;
122
+ private resolveTextSectionClassName;
117
123
  private renderLayoutItems;
118
124
  private createChartHost;
119
125
  private createReadyPromise;
@@ -122,9 +128,16 @@ export declare class ChartGroup {
122
128
  private createRasterExportOptions;
123
129
  private exportPdfContent;
124
130
  private resolveExportLayoutState;
131
+ private createExportLayoutState;
132
+ private createExportRenderContext;
133
+ private resolveBaseExportHeight;
134
+ private createExportComponent;
135
+ private runExportHooks;
125
136
  private resolveLiveChartHeightOverride;
126
137
  private exportLayoutItems;
127
138
  private composeExportSvg;
139
+ private syncAccessibleLabelFromSvg;
140
+ private appendExportTextSections;
128
141
  private validateResponsiveConfig;
129
142
  private validateItemResponsiveConfig;
130
143
  private resolveResponsiveConfigForWidth;