@internetstiftelsen/charts 0.7.1 → 0.9.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
@@ -6,7 +6,9 @@ A framework-agnostic, composable charting library built on D3.js with TypeScript
6
6
 
7
7
  - **Framework Agnostic** - Works with vanilla JS, React, Vue, Svelte, or any framework
8
8
  - **Composable Architecture** - Build charts by composing components
9
- - **Multiple Chart Types** - XYChart (lines, areas, bars), DonutChart, PieChart, and GaugeChart
9
+ - **Multiple Chart Types** - XYChart (lines, areas, bars), WordCloudChart, DonutChart, PieChart, and GaugeChart
10
+ - **Optional Gauge Animation** - Animate gauge value transitions with `gauge.animate`
11
+ - **Stacking Control** - Bar stacking modes with optional reversed visual series order
10
12
  - **Flexible Scales** - Band, linear, time, and logarithmic scales
11
13
  - **Auto Resize** - Built-in ResizeObserver handles responsive behavior
12
14
  - **Responsive Policy** - Chart-level container-query overrides for theme and components
@@ -80,6 +82,37 @@ chart
80
82
  chart.render('#chart-container');
81
83
  ```
82
84
 
85
+ ## Word Cloud
86
+
87
+ ```javascript
88
+ import { WordCloudChart } from '@internetstiftelsen/charts/word-cloud-chart';
89
+
90
+ const data = [
91
+ { word: 'internet', count: 96 },
92
+ { word: 'social', count: 82 },
93
+ { word: 'news', count: 75 },
94
+ ];
95
+
96
+ const chart = new WordCloudChart({
97
+ data,
98
+ wordCloud: {
99
+ minValue: 5,
100
+ minWordLength: 3,
101
+ minFontSize: 3,
102
+ maxFontSize: 20,
103
+ padding: 1,
104
+ spiral: 'archimedean',
105
+ },
106
+ });
107
+
108
+ chart.render('#word-cloud');
109
+ ```
110
+
111
+ `minFontSize` and `maxFontSize` are percentages of the smaller plot-area
112
+ dimension and define the relative size range passed into `d3-cloud`. The chart
113
+ expects flat `{ word, count }` rows, aggregates duplicate words after trimming,
114
+ and maps theme typography and colors directly into the layout and rendered SVG.
115
+
83
116
  ## Export
84
117
 
85
118
  `chart.export()` supports `svg`, `json`, `csv`, `xlsx`, `png`, `jpg`, and `pdf`.
@@ -103,12 +136,9 @@ It auto-detects grouped and normal (flat) table layouts.
103
136
  import { toChartData } from '@internetstiftelsen/charts/utils';
104
137
  import { XYChart } from '@internetstiftelsen/charts/xy-chart';
105
138
 
106
- const data = toChartData(
107
- '\t\tDaily\tWeekly\nAll users\tSegment A\t85%\t92%\n\tSegment B\t84%\t91%',
108
- {
109
- categoryKey: 'Category',
110
- },
111
- );
139
+ const data = toChartData('\t\tDaily\tWeekly\nAll users\tSegment A\t85%\t92%\n\tSegment B\t84%\t91%', {
140
+ categoryKey: 'Category',
141
+ });
112
142
 
113
143
  const chart = new XYChart({ data });
114
144
  chart.render('#chart-container');
@@ -117,16 +147,32 @@ chart.render('#chart-container');
117
147
  The parser supports JSON-escaped string payloads and grouped carry-forward row
118
148
  structure (blank first column on continuation rows).
119
149
 
150
+ Supported input shapes:
151
+
152
+ - Plain tab-delimited strings
153
+ - JSON-escaped string payloads
154
+
155
+ Auto-detection behavior:
156
+
157
+ - Grouped rows when a carry-forward group structure is present
158
+ - Flat rows when no grouped continuation rows are detected
159
+
160
+ Grouped parsing rules:
161
+
162
+ - Header row starts with two structural columns (`group`, `category`) before metrics
163
+ - Continuation rows leave the first column blank to inherit the previous group
164
+ - Blank separator rows are ignored
165
+
120
166
  ## Documentation
121
167
 
122
168
  - [Getting Started](./docs/getting-started.md) - Installation, Vanilla JS, React integration
123
169
  - [XYChart](./docs/xy-chart.md) - Line, area, and bar charts API
170
+ - [WordCloudChart](./docs/word-cloud-chart.md) - Word frequency visualization API
124
171
  - [DonutChart](./docs/donut-chart.md) - Donut/pie charts API
125
172
  - [PieChart](./docs/pie-chart.md) - Pie chart API
126
173
  - [GaugeChart](./docs/gauge-chart.md) - Gauge chart API
127
174
  - [Components](./docs/components.md) - Axes, Grid, Tooltip, Legend, Title
128
175
  - [Theming](./docs/theming.md) - Colors, fonts, and styling
129
- - [Advanced](./docs/advanced.md) - Scales, TypeScript, architecture, performance
130
176
 
131
177
  ## Browser Support
132
178
 
package/area.d.ts CHANGED
@@ -19,7 +19,6 @@ export declare class Area implements ChartComponent<AreaConfigBase> {
19
19
  constructor(config: AreaConfig);
20
20
  getExportConfig(): AreaConfigBase;
21
21
  createExportComponent(override?: Partial<AreaConfigBase>): ChartComponent;
22
- private getScaledPosition;
23
22
  private getStackValues;
24
23
  render(plotGroup: Selection<SVGGElement, undefined, null, undefined>, data: DataItem[], xKey: string, x: D3Scale, y: D3Scale, parseValue: (value: unknown) => number, xScaleType: ScaleType | undefined, theme: ChartTheme, stackingContext?: AreaStackingContext, valueLabelLayer?: Selection<SVGGElement, undefined, null, undefined>): void;
25
24
  private renderValueLabels;
package/area.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { area, curveBasis, curveCardinal, curveLinear, curveMonotoneX, curveNatural, curveStep, line, } from 'd3';
2
2
  import { mergeDeep, sanitizeForCSS } from './utils.js';
3
+ import { getScalePosition } from './scale-utils.js';
3
4
  const AREA_CURVE_FACTORIES = {
4
5
  linear: curveLinear,
5
6
  monotone: curveMonotoneX,
@@ -132,24 +133,6 @@ export class Area {
132
133
  exportHooks: this.exportHooks,
133
134
  });
134
135
  }
135
- getScaledPosition(data, key, scale, scaleType) {
136
- const value = data[key];
137
- let scaledValue;
138
- switch (scaleType) {
139
- case 'band':
140
- scaledValue = String(value);
141
- break;
142
- case 'time':
143
- scaledValue =
144
- value instanceof Date ? value : new Date(String(value));
145
- break;
146
- case 'linear':
147
- case 'log':
148
- scaledValue = typeof value === 'number' ? value : Number(value);
149
- break;
150
- }
151
- return scale(scaledValue) || 0;
152
- }
153
136
  getStackValues(dataPoint, xKey, parseValue, stackingContext) {
154
137
  const value = parseValue(dataPoint[this.dataKey]);
155
138
  if (!stackingContext || stackingContext.mode === 'none') {
@@ -176,7 +159,7 @@ export class Area {
176
159
  }
177
160
  render(plotGroup, data, xKey, x, y, parseValue, xScaleType = 'band', theme, stackingContext, valueLabelLayer) {
178
161
  const getXPosition = (d) => {
179
- const scaled = this.getScaledPosition(d.data, xKey, x, xScaleType);
162
+ const scaled = getScalePosition(x, d.data[xKey], xScaleType);
180
163
  return scaled + (x.bandwidth ? x.bandwidth() / 2 : 0);
181
164
  };
182
165
  const hasValidValue = (d) => {
package/bar.d.ts CHANGED
@@ -13,7 +13,6 @@ export declare class Bar implements ChartComponent<BarConfigBase> {
13
13
  constructor(config: BarConfig);
14
14
  getExportConfig(): BarConfigBase;
15
15
  createExportComponent(override?: Partial<BarConfigBase>): ChartComponent;
16
- private getScaledPosition;
17
16
  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;
18
17
  private renderVertical;
19
18
  private renderHorizontal;
package/bar.js CHANGED
@@ -1,4 +1,19 @@
1
1
  import { getContrastTextColor, sanitizeForCSS, mergeDeep } from './utils.js';
2
+ import { getScalePosition } from './scale-utils.js';
3
+ const LABEL_INSET_DEFAULT = 4;
4
+ const LABEL_INSET_STACKED = 6;
5
+ const LABEL_MIN_PADDING_DEFAULT = 8;
6
+ const LABEL_MIN_PADDING_STACKED = 16;
7
+ const LAYER_LABEL_GAP = 6;
8
+ function getLabelSpacing(mode) {
9
+ const stacked = mode !== 'none';
10
+ return {
11
+ inset: stacked ? LABEL_INSET_STACKED : LABEL_INSET_DEFAULT,
12
+ minPadding: stacked
13
+ ? LABEL_MIN_PADDING_STACKED
14
+ : LABEL_MIN_PADDING_DEFAULT,
15
+ };
16
+ }
2
17
  export class Bar {
3
18
  constructor(config) {
4
19
  Object.defineProperty(this, "type", {
@@ -74,24 +89,6 @@ export class Bar {
74
89
  exportHooks: this.exportHooks,
75
90
  });
76
91
  }
77
- getScaledPosition(data, key, scale, scaleType) {
78
- const value = data[key];
79
- let scaledValue;
80
- switch (scaleType) {
81
- case 'band':
82
- scaledValue = String(value);
83
- break;
84
- case 'time':
85
- scaledValue =
86
- value instanceof Date ? value : new Date(String(value));
87
- break;
88
- case 'linear':
89
- case 'log':
90
- scaledValue = typeof value === 'number' ? value : Number(value);
91
- break;
92
- }
93
- return scale(scaledValue) || 0;
94
- }
95
92
  render(plotGroup, data, xKey, x, y, parseValue, xScaleType = 'band', theme, stackingContext) {
96
93
  if (this.orientation === 'vertical') {
97
94
  this.renderVertical(plotGroup, data, xKey, x, y, parseValue, xScaleType, stackingContext);
@@ -164,7 +161,7 @@ export class Bar {
164
161
  .attr('class', `bar-${sanitizedKey}`)
165
162
  .attr('data-index', (_, i) => i)
166
163
  .attr('x', (d) => {
167
- const xPos = this.getScaledPosition(d, xKey, x, xScaleType);
164
+ const xPos = getScalePosition(x, d[xKey], xScaleType);
168
165
  return xScaleType === 'band'
169
166
  ? xPos + barOffset
170
167
  : xPos - barWidth / 2;
@@ -287,7 +284,7 @@ export class Bar {
287
284
  }
288
285
  })
289
286
  .attr('y', (d) => {
290
- const yPos = this.getScaledPosition(d, xKey, y, yScaleType);
287
+ const yPos = getScalePosition(y, d[xKey], yScaleType);
291
288
  return yScaleType === 'band'
292
289
  ? yPos + barOffset
293
290
  : yPos - barHeight / 2;
@@ -374,7 +371,7 @@ export class Bar {
374
371
  const categoryKey = String(d[xKey]);
375
372
  const value = parseValue(d[this.dataKey]);
376
373
  const valueText = String(value);
377
- const xPos = this.getScaledPosition(d, xKey, x, xScaleType);
374
+ const xPos = getScalePosition(x, d[xKey], xScaleType);
378
375
  const barColor = this.colorAdapter
379
376
  ? this.colorAdapter(d, i)
380
377
  : this.fill;
@@ -426,76 +423,42 @@ export class Bar {
426
423
  }
427
424
  }
428
425
  else {
429
- // Inside the bar - with special handling for layer mode
430
- if (mode === 'layer') {
431
- const totalSeries = stackingContext?.totalSeries ?? 1;
432
- const seriesIndex = stackingContext?.seriesIndex ?? 0;
433
- const isTopLayer = seriesIndex === totalSeries - 1;
434
- switch (insidePosition) {
435
- case 'top':
436
- // For layer mode + inside + top: check if there's enough space in the gap
437
- if (seriesIndex < totalSeries - 1) {
438
- // Calculate the gap to the next layer
439
- const nextLayerScaleFactor = 1 - ((seriesIndex + 1) / totalSeries) * 0.7;
440
- const nextLayerWidth = (this.maxBarSize
441
- ? Math.min(bandwidth, this.maxBarSize)
442
- : bandwidth) * nextLayerScaleFactor;
443
- const gap = (barWidth - nextLayerWidth) / 2;
444
- const marginBelow = 4; // Minimum margin below text
445
- if (boxHeight + marginBelow <= gap) {
446
- labelY =
447
- barTop + boxHeight / 2 + marginBelow;
448
- }
449
- else {
450
- shouldRender = false;
451
- }
452
- }
453
- else {
454
- // Top layer - use normal top position if it fits
455
- labelY = barTop + boxHeight / 2 + 4;
456
- if (boxHeight + 8 > barHeight) {
457
- shouldRender = false;
458
- }
459
- }
460
- break;
461
- case 'middle':
462
- // For layer mode + inside + middle: only show what fits
463
- labelY = (barTop + barBottom) / 2;
464
- if (boxHeight + 8 > barHeight) {
465
- shouldRender = false;
466
- }
467
- break;
468
- case 'bottom':
469
- // For layer mode + inside + bottom: only show for top layer if it fits
470
- if (isTopLayer) {
471
- labelY = barBottom - boxHeight / 2 - 4;
472
- if (boxHeight + 8 > barHeight) {
473
- shouldRender = false;
474
- }
475
- }
476
- else {
477
- shouldRender = false;
478
- }
479
- break;
480
- }
426
+ if (mode === 'layer' && insidePosition === 'bottom') {
427
+ // Bottom labels in layer mode are visually ambiguous and often hidden by overlap.
428
+ shouldRender = false;
481
429
  }
482
430
  else {
483
- // Non-layer modes - use existing logic
431
+ const { inset, minPadding } = getLabelSpacing(mode);
484
432
  switch (insidePosition) {
485
433
  case 'top':
486
- labelY = barTop + boxHeight / 2 + 4;
434
+ labelY = barTop + boxHeight / 2 + inset;
487
435
  break;
488
436
  case 'middle':
489
437
  labelY = (barTop + barBottom) / 2;
490
438
  break;
491
439
  case 'bottom':
492
- labelY = barBottom - boxHeight / 2 - 4;
440
+ labelY = barBottom - boxHeight / 2 - inset;
493
441
  break;
494
442
  }
495
443
  // Check if it fits inside the bar
496
- if (boxHeight + 8 > barHeight) {
444
+ if (boxHeight + minPadding > barHeight) {
497
445
  shouldRender = false;
498
446
  }
447
+ // In layer mode, check the label fits in the visible gap
448
+ // above the next layer's bar top
449
+ if (shouldRender &&
450
+ mode === 'layer' &&
451
+ insidePosition === 'top' &&
452
+ stackingContext?.nextLayerData) {
453
+ const nextValue = stackingContext.nextLayerData.get(categoryKey);
454
+ if (nextValue !== undefined) {
455
+ const nextBarTop = y(nextValue) || 0;
456
+ const labelBottom = labelY + boxHeight / 2;
457
+ if (labelBottom + LAYER_LABEL_GAP > nextBarTop) {
458
+ shouldRender = false;
459
+ }
460
+ }
461
+ }
499
462
  }
500
463
  }
501
464
  tempText.remove();
@@ -592,7 +555,7 @@ export class Bar {
592
555
  const categoryKey = String(d[xKey]);
593
556
  const value = parseValue(d[this.dataKey]);
594
557
  const valueText = String(value);
595
- const yPos = this.getScaledPosition(d, xKey, y, yScaleType);
558
+ const yPos = getScalePosition(y, d[xKey], yScaleType);
596
559
  const barColor = this.colorAdapter
597
560
  ? this.colorAdapter(d, i)
598
561
  : this.fill;
@@ -644,78 +607,43 @@ export class Bar {
644
607
  }
645
608
  }
646
609
  else {
647
- // Inside the bar - with special handling for layer mode
648
- if (mode === 'layer') {
649
- const totalSeries = stackingContext?.totalSeries ?? 1;
650
- const seriesIndex = stackingContext?.seriesIndex ?? 0;
651
- const isTopLayer = seriesIndex === totalSeries - 1;
652
- // Map top/middle/bottom to start/middle/end for horizontal
653
- switch (insidePosition) {
654
- case 'top': // start of bar (left side)
655
- // For layer mode + inside + top(left): check if there's enough space in the gap
656
- if (seriesIndex < totalSeries - 1) {
657
- // Calculate the gap to the next layer
658
- const nextLayerScaleFactor = 1 - ((seriesIndex + 1) / totalSeries) * 0.7;
659
- const nextLayerHeight = (this.maxBarSize
660
- ? Math.min(bandwidth, this.maxBarSize)
661
- : bandwidth) * nextLayerScaleFactor;
662
- const gap = (barHeight - nextLayerHeight) / 2;
663
- const marginRight = 4; // Minimum margin to the right of text
664
- if (boxWidth + marginRight <= gap) {
665
- labelX =
666
- barLeft + boxWidth / 2 + marginRight;
667
- }
668
- else {
669
- shouldRender = false;
670
- }
671
- }
672
- else {
673
- // Top layer - use normal left position if it fits
674
- labelX = barLeft + boxWidth / 2 + 4;
675
- if (boxWidth + 8 > barWidth) {
676
- shouldRender = false;
677
- }
678
- }
679
- break;
680
- case 'middle':
681
- // For layer mode + inside + middle: only show what fits
682
- labelX = (barLeft + barRight) / 2;
683
- if (boxWidth + 8 > barWidth) {
684
- shouldRender = false;
685
- }
686
- break;
687
- case 'bottom': // end of bar (right side)
688
- // For layer mode + inside + bottom(right): only show for top layer if it fits
689
- if (isTopLayer) {
690
- labelX = barRight - boxWidth / 2 - 4;
691
- if (boxWidth + 8 > barWidth) {
692
- shouldRender = false;
693
- }
694
- }
695
- else {
696
- shouldRender = false;
697
- }
698
- break;
699
- }
610
+ // Map top/middle/bottom to start/middle/end for horizontal
611
+ if (mode === 'layer' && insidePosition === 'bottom') {
612
+ // Bottom labels in layer mode are visually ambiguous and often hidden by overlap.
613
+ shouldRender = false;
700
614
  }
701
615
  else {
702
- // Non-layer modes - use existing logic
703
- // Map top/middle/bottom to start/middle/end for horizontal
616
+ const { inset, minPadding } = getLabelSpacing(mode);
704
617
  switch (insidePosition) {
705
618
  case 'top': // start of bar (left side)
706
- labelX = barLeft + boxWidth / 2 + 4;
619
+ labelX = barLeft + boxWidth / 2 + inset;
707
620
  break;
708
621
  case 'middle':
709
622
  labelX = (barLeft + barRight) / 2;
710
623
  break;
711
624
  case 'bottom': // end of bar (right side)
712
- labelX = barRight - boxWidth / 2 - 4;
625
+ labelX = barRight - boxWidth / 2 - inset;
713
626
  break;
714
627
  }
715
628
  // Check if it fits inside the bar
716
- if (boxWidth + 8 > barWidth) {
629
+ if (boxWidth + minPadding > barWidth) {
717
630
  shouldRender = false;
718
631
  }
632
+ // In layer mode, check the label fits in the visible gap
633
+ // before the next layer's bar end
634
+ if (shouldRender &&
635
+ mode === 'layer' &&
636
+ insidePosition === 'top' &&
637
+ stackingContext?.nextLayerData) {
638
+ const nextValue = stackingContext.nextLayerData.get(categoryKey);
639
+ if (nextValue !== undefined) {
640
+ const nextBarRight = x(nextValue) || 0;
641
+ const labelRight = labelX + boxWidth / 2;
642
+ if (labelRight + LAYER_LABEL_GAP > nextBarRight) {
643
+ shouldRender = false;
644
+ }
645
+ }
646
+ }
719
647
  }
720
648
  }
721
649
  tempText.remove();
package/base-chart.d.ts CHANGED
@@ -18,6 +18,34 @@ type ResponsiveOverrides = {
18
18
  theme?: DeepPartial<ChartTheme>;
19
19
  components: Map<ChartComponent, Record<string, unknown>>;
20
20
  };
21
+ type BaseLayoutComponentsOptions = {
22
+ title?: boolean;
23
+ xAxis?: boolean;
24
+ yAxis?: boolean;
25
+ inlineLegend?: boolean;
26
+ };
27
+ type BaseExportComponentsOptions = {
28
+ title?: boolean;
29
+ grid?: boolean;
30
+ xAxis?: boolean;
31
+ yAxis?: boolean;
32
+ tooltip?: boolean;
33
+ legend?: boolean;
34
+ };
35
+ export type BaseLayoutContext = {
36
+ svg: Selection<SVGSVGElement, undefined, null, undefined>;
37
+ svgNode: SVGSVGElement;
38
+ };
39
+ export type BaseRenderContext = BaseLayoutContext & {
40
+ plotGroup: Selection<SVGGElement, undefined, null, undefined>;
41
+ plotArea: PlotAreaBounds;
42
+ };
43
+ type ComponentSlot<TComponent extends ChartComponent = ChartComponent> = {
44
+ type: TComponent['type'];
45
+ get: () => TComponent | null;
46
+ set: (component: TComponent | null) => void;
47
+ onRegister?: (component: TComponent) => void;
48
+ };
21
49
  export type BaseChartConfig = {
22
50
  data: ChartData;
23
51
  theme?: Partial<ChartTheme>;
@@ -49,12 +77,14 @@ export declare abstract class BaseChart {
49
77
  protected resizeObserver: ResizeObserver | null;
50
78
  protected layoutManager: LayoutManager;
51
79
  protected plotArea: PlotAreaBounds | null;
80
+ private readyPromise;
52
81
  private disconnectedLegendContainer;
82
+ private renderThemeOverride;
53
83
  protected constructor(config: BaseChartConfig);
54
84
  /**
55
85
  * Adds a component (axis, grid, tooltip, etc.) to the chart
56
86
  */
57
- abstract addChild(component: ChartComponent): this;
87
+ addChild(component: ChartComponent): this;
58
88
  /**
59
89
  * Renders the chart to the specified target element
60
90
  */
@@ -71,13 +101,18 @@ export declare abstract class BaseChart {
71
101
  }): ResponsiveRenderContext;
72
102
  private resolveBreakpointName;
73
103
  private resolveRenderTheme;
74
- private applyThemeOverride;
104
+ private applyRenderTheme;
105
+ protected get renderTheme(): ChartTheme;
75
106
  /**
76
107
  * Get layout-aware components in order
77
108
  * Override in subclasses to provide chart-specific components
78
109
  */
79
110
  protected getLayoutComponents(): LayoutAwareComponent[];
111
+ protected getBaseLayoutComponents(options: BaseLayoutComponentsOptions): LayoutAwareComponent[];
80
112
  protected getExportComponents(): ChartComponent[];
113
+ protected getOverrideableComponents(): ChartComponent[];
114
+ protected getBaseExportComponents(options: BaseExportComponentsOptions): ChartComponent[];
115
+ protected registerBaseComponent(component: ChartComponent): boolean;
81
116
  protected collectExportOverrides(context: ExportRenderContext): Map<ChartComponent, Record<string, unknown>>;
82
117
  protected collectResponsiveOverrides(context: ResponsiveRenderContext): ResponsiveOverrides;
83
118
  protected runExportHooks(context: ExportHookContext): void;
@@ -85,7 +120,14 @@ export declare abstract class BaseChart {
85
120
  private createOverrideComponents;
86
121
  protected applyComponentOverrides(overrides: Map<ChartComponent, ChartComponent>): () => void;
87
122
  private renderExportChart;
88
- protected prepareLayout(): void;
123
+ protected renderTitle(svg: Selection<SVGSVGElement, undefined, null, undefined>): void;
124
+ protected renderInlineLegend(svg: Selection<SVGSVGElement, undefined, null, undefined>): void;
125
+ protected measureInlineLegend(svgNode: SVGSVGElement): void;
126
+ protected filterVisibleItems<T>(items: T[], getDataKey: (item: T) => string): T[];
127
+ protected validateSourceData(_data: ChartData): void;
128
+ protected syncDerivedState(_previousData?: DataItem[]): void;
129
+ protected initializeDataState(): void;
130
+ protected prepareLayout(context: BaseLayoutContext): void;
89
131
  /**
90
132
  * Setup ResizeObserver for automatic resize handling
91
133
  */
@@ -93,8 +135,10 @@ export declare abstract class BaseChart {
93
135
  /**
94
136
  * Subclasses must implement this method to define their rendering logic
95
137
  */
96
- protected abstract renderChart(): void;
138
+ protected abstract renderChart(context: BaseRenderContext): void;
97
139
  protected abstract createExportChart(): BaseChart;
140
+ protected setReadyPromise(promise: Promise<void>): void;
141
+ whenReady(): Promise<void>;
98
142
  protected getLegendSeries(): LegendSeries[];
99
143
  getLegendItems(): LegendItem[];
100
144
  isLegendSeriesVisible(dataKey: string): boolean;
@@ -114,6 +158,11 @@ export declare abstract class BaseChart {
114
158
  private resolveDisconnectedLegendHost;
115
159
  private cleanupDisconnectedLegendContainer;
116
160
  protected parseValue(value: unknown): number;
161
+ protected rerender(): void;
162
+ protected tryRegisterComponent(component: ChartComponent, slots: readonly ComponentSlot[]): boolean;
163
+ protected applySlotOverrides(overrides: Map<ChartComponent, ChartComponent>, slots: readonly ComponentSlot[]): () => void;
164
+ protected applyArrayComponentOverrides<TComponent extends ChartComponent>(components: TComponent[], overrides: Map<ChartComponent, ChartComponent>, isComponent: (component: ChartComponent) => component is TComponent): () => void;
165
+ private getBaseComponentSlots;
117
166
  /**
118
167
  * Exports the chart in the specified format
119
168
  * @param format - The export format
@@ -131,7 +180,7 @@ export declare abstract class BaseChart {
131
180
  private exportXLSX;
132
181
  private exportImage;
133
182
  private exportPDF;
134
- protected exportSVG(options?: ExportOptions, formatForHooks?: VisualExportFormat): string;
183
+ protected exportSVG(options?: ExportOptions, formatForHooks?: VisualExportFormat): Promise<string>;
135
184
  protected exportJSON(): string;
136
185
  }
137
186
  export {};