@internetstiftelsen/charts 0.9.0 → 0.9.1

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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/{area.d.ts → dist/area.d.ts} +1 -1
  4. package/{bar.d.ts → dist/bar.d.ts} +3 -4
  5. package/{bar.js → dist/bar.js} +3 -11
  6. package/{base-chart.d.ts → dist/base-chart.d.ts} +30 -18
  7. package/{base-chart.js → dist/base-chart.js} +170 -50
  8. package/dist/chart-interface.d.ts +19 -0
  9. package/{donut-center-content.d.ts → dist/donut-center-content.d.ts} +1 -1
  10. package/{donut-chart.d.ts → dist/donut-chart.d.ts} +19 -4
  11. package/{donut-chart.js → dist/donut-chart.js} +140 -2
  12. package/{gauge-chart.d.ts → dist/gauge-chart.d.ts} +2 -2
  13. package/{gauge-chart.js → dist/gauge-chart.js} +2 -0
  14. package/{grid.d.ts → dist/grid.d.ts} +1 -1
  15. package/{layout-manager.d.ts → dist/layout-manager.d.ts} +5 -5
  16. package/{legend.d.ts → dist/legend.d.ts} +3 -1
  17. package/{legend.js → dist/legend.js} +32 -0
  18. package/{line.d.ts → dist/line.d.ts} +1 -1
  19. package/{pie-chart.d.ts → dist/pie-chart.d.ts} +4 -11
  20. package/{pie-chart.js → dist/pie-chart.js} +23 -21
  21. package/{radial-chart-base.js → dist/radial-chart-base.js} +3 -1
  22. package/{theme.d.ts → dist/theme.d.ts} +2 -0
  23. package/{theme.js → dist/theme.js} +24 -29
  24. package/{title.d.ts → dist/title.d.ts} +1 -1
  25. package/{tooltip.d.ts → dist/tooltip.d.ts} +1 -1
  26. package/{tooltip.js → dist/tooltip.js} +239 -74
  27. package/{types.d.ts → dist/types.d.ts} +27 -10
  28. package/{utils.d.ts → dist/utils.d.ts} +0 -2
  29. package/{utils.js → dist/utils.js} +0 -5
  30. package/{word-cloud-chart.d.ts → dist/word-cloud-chart.d.ts} +1 -1
  31. package/{word-cloud-chart.js → dist/word-cloud-chart.js} +2 -0
  32. package/{x-axis.d.ts → dist/x-axis.d.ts} +2 -1
  33. package/{x-axis.js → dist/x-axis.js} +18 -14
  34. package/{xy-chart.d.ts → dist/xy-chart.d.ts} +8 -5
  35. package/{xy-chart.js → dist/xy-chart.js} +31 -5
  36. package/{y-axis.d.ts → dist/y-axis.d.ts} +1 -1
  37. package/{y-axis.js → dist/y-axis.js} +4 -4
  38. package/package.json +38 -36
  39. package/chart-interface.d.ts +0 -13
  40. /package/{area.js → dist/area.js} +0 -0
  41. /package/{chart-interface.js → dist/chart-interface.js} +0 -0
  42. /package/{donut-center-content.js → dist/donut-center-content.js} +0 -0
  43. /package/{export-image.d.ts → dist/export-image.d.ts} +0 -0
  44. /package/{export-image.js → dist/export-image.js} +0 -0
  45. /package/{export-pdf.d.ts → dist/export-pdf.d.ts} +0 -0
  46. /package/{export-pdf.js → dist/export-pdf.js} +0 -0
  47. /package/{export-tabular.d.ts → dist/export-tabular.d.ts} +0 -0
  48. /package/{export-tabular.js → dist/export-tabular.js} +0 -0
  49. /package/{export-xlsx.d.ts → dist/export-xlsx.d.ts} +0 -0
  50. /package/{export-xlsx.js → dist/export-xlsx.js} +0 -0
  51. /package/{grid.js → dist/grid.js} +0 -0
  52. /package/{grouped-data.d.ts → dist/grouped-data.d.ts} +0 -0
  53. /package/{grouped-data.js → dist/grouped-data.js} +0 -0
  54. /package/{grouped-tabular.d.ts → dist/grouped-tabular.d.ts} +0 -0
  55. /package/{grouped-tabular.js → dist/grouped-tabular.js} +0 -0
  56. /package/{layout-manager.js → dist/layout-manager.js} +0 -0
  57. /package/{line.js → dist/line.js} +0 -0
  58. /package/{radial-chart-base.d.ts → dist/radial-chart-base.d.ts} +0 -0
  59. /package/{scale-utils.d.ts → dist/scale-utils.d.ts} +0 -0
  60. /package/{scale-utils.js → dist/scale-utils.js} +0 -0
  61. /package/{title.js → dist/title.js} +0 -0
  62. /package/{types.js → dist/types.js} +0 -0
  63. /package/{validation.d.ts → dist/validation.d.ts} +0 -0
  64. /package/{validation.js → dist/validation.js} +0 -0
@@ -14,7 +14,7 @@ export declare class Title implements LayoutAwareComponent<TitleConfigBase> {
14
14
  private readonly marginBottom;
15
15
  constructor(config: TitleConfig);
16
16
  getExportConfig(): TitleConfigBase;
17
- createExportComponent(override?: Partial<TitleConfigBase>): LayoutAwareComponent;
17
+ createExportComponent(override?: Partial<TitleConfigBase>): LayoutAwareComponent<TitleConfigBase>;
18
18
  /**
19
19
  * Returns the space required by the title
20
20
  */
@@ -19,7 +19,7 @@ export declare class Tooltip implements ChartComponent<TooltipConfigBase> {
19
19
  private tooltipDiv;
20
20
  constructor(config?: TooltipConfig);
21
21
  getExportConfig(): TooltipConfigBase;
22
- createExportComponent(override?: Partial<TooltipConfigBase>): ChartComponent;
22
+ createExportComponent(override?: Partial<TooltipConfigBase>): ChartComponent<TooltipConfigBase>;
23
23
  initialize(theme: ChartTheme): void;
24
24
  attachToArea(svg: Selection<SVGSVGElement, undefined, null, undefined>, data: DataItem[], series: (Line | Bar | Area)[], xKey: string, x: D3Scale, y: D3Scale, theme: ChartTheme, plotArea: PlotAreaBounds, parseValue: (value: unknown) => number, isHorizontal?: boolean, categoryScaleType?: ScaleType, resolveSeriesValue?: (series: Line | Bar | Area, dataPoint: DataItem, index: number) => number): void;
25
25
  cleanup(): void;
@@ -93,8 +93,23 @@ export class Tooltip {
93
93
  }
94
94
  return parseValue(rawValue);
95
95
  }) {
96
- if (!this.tooltipDiv)
96
+ if (!this.tooltipDiv) {
97
97
  return;
98
+ }
99
+ const normalizeFormatterValue = (value) => {
100
+ if (value === null ||
101
+ value === undefined ||
102
+ typeof value === 'string' ||
103
+ typeof value === 'number' ||
104
+ typeof value === 'boolean' ||
105
+ value instanceof Date) {
106
+ return value;
107
+ }
108
+ return String(value);
109
+ };
110
+ if (data.length === 0) {
111
+ return;
112
+ }
98
113
  const tooltip = this.tooltipDiv;
99
114
  const formatter = this.formatter;
100
115
  const labelFormatter = this.labelFormatter;
@@ -122,6 +137,72 @@ export class Tooltip {
122
137
  const scaled = y(getCategoryScaleValue(yValue, categoryScaleType));
123
138
  return (scaled || 0) + (y.bandwidth ? y.bandwidth() / 2 : 0);
124
139
  };
140
+ const stripHtml = (content) => {
141
+ return content
142
+ .replace(/<br\s*\/?>/gi, '. ')
143
+ .replace(/<[^>]+>/g, ' ')
144
+ .replace(/\s+/g, ' ')
145
+ .trim();
146
+ };
147
+ const buildTooltipContent = (dataPoint) => {
148
+ if (customFormatter) {
149
+ return customFormatter(dataPoint, series);
150
+ }
151
+ const labelValue = dataPoint[xKey];
152
+ const label = labelFormatter
153
+ ? labelFormatter(String(labelValue), dataPoint)
154
+ : String(labelValue);
155
+ let content = `<strong>${label}</strong><br/>`;
156
+ series.forEach((s) => {
157
+ const value = dataPoint[s.dataKey];
158
+ if (formatter) {
159
+ content +=
160
+ formatter(s.dataKey, normalizeFormatterValue(value), dataPoint) + '<br/>';
161
+ return;
162
+ }
163
+ content += `${s.dataKey}: ${value}<br/>`;
164
+ });
165
+ return content;
166
+ };
167
+ const buildAccessibleLabel = (dataPoint) => {
168
+ if (customFormatter) {
169
+ return stripHtml(customFormatter(dataPoint, series));
170
+ }
171
+ const labelValue = dataPoint[xKey];
172
+ const label = labelFormatter
173
+ ? labelFormatter(String(labelValue), dataPoint)
174
+ : String(labelValue);
175
+ const parts = [label];
176
+ series.forEach((s) => {
177
+ const value = dataPoint[s.dataKey];
178
+ if (formatter) {
179
+ parts.push(stripHtml(formatter(s.dataKey, normalizeFormatterValue(value), dataPoint)));
180
+ return;
181
+ }
182
+ parts.push(`${s.dataKey}: ${value === null || value === undefined
183
+ ? 'no data'
184
+ : value}`);
185
+ });
186
+ return parts.join('. ');
187
+ };
188
+ const isTooltipFocusTarget = (element) => {
189
+ return (element instanceof SVGElement &&
190
+ element.classList.contains('tooltip-focus-target'));
191
+ };
192
+ const dataPointPositions = data.map((dataPoint) => isHorizontal ? getYPosition(dataPoint) : getXPosition(dataPoint));
193
+ const getClosestIndexFromPointer = (mouseX, mouseY) => {
194
+ const pointerPosition = isHorizontal ? mouseY : mouseX;
195
+ let closestIndex = 0;
196
+ let minDistance = Math.abs(pointerPosition - dataPointPositions[0]);
197
+ for (let i = 1; i < dataPointPositions.length; i++) {
198
+ const distance = Math.abs(pointerPosition - dataPointPositions[i]);
199
+ if (distance < minDistance) {
200
+ minDistance = distance;
201
+ closestIndex = i;
202
+ }
203
+ }
204
+ return closestIndex;
205
+ };
125
206
  // Create overlay rect for mouse tracking using plot area bounds
126
207
  const overlay = svg
127
208
  .append('rect')
@@ -130,6 +211,7 @@ export class Tooltip {
130
211
  .attr('y', plotArea.top)
131
212
  .attr('width', plotArea.width)
132
213
  .attr('height', plotArea.height)
214
+ .attr('aria-hidden', 'true')
133
215
  .style('fill', 'none')
134
216
  .style('pointer-events', 'all');
135
217
  const lineSeries = series.filter((s) => s.type === 'line' || s.type === 'area');
@@ -144,39 +226,23 @@ export class Tooltip {
144
226
  .attr('fill', theme.line.point.color || seriesColor)
145
227
  .attr('stroke', theme.line.point.strokeColor || seriesColor)
146
228
  .attr('stroke-width', theme.line.point.strokeWidth)
229
+ .attr('aria-hidden', 'true')
147
230
  .style('opacity', 0)
148
231
  .style('pointer-events', 'none');
149
232
  });
150
- overlay
151
- .on('mousemove', (event) => {
152
- const [mouseX, mouseY] = pointer(event, svg.node());
153
- let closestIndex = 0;
154
- let dataPointPosition;
155
- if (isHorizontal) {
156
- const yPositions = data.map((d) => getYPosition(d));
157
- let minDistance = Math.abs(mouseY - yPositions[0]);
158
- for (let i = 1; i < yPositions.length; i++) {
159
- const distance = Math.abs(mouseY - yPositions[i]);
160
- if (distance < minDistance) {
161
- minDistance = distance;
162
- closestIndex = i;
163
- }
164
- }
165
- dataPointPosition = yPositions[closestIndex];
166
- }
167
- else {
168
- const xPositions = data.map((d) => getXPosition(d));
169
- let minDistance = Math.abs(mouseX - xPositions[0]);
170
- for (let i = 1; i < xPositions.length; i++) {
171
- const distance = Math.abs(mouseX - xPositions[i]);
172
- if (distance < minDistance) {
173
- minDistance = distance;
174
- closestIndex = i;
175
- }
176
- }
177
- dataPointPosition = xPositions[closestIndex];
233
+ const clearVisualState = () => {
234
+ focusCircles.forEach((circle) => circle.style('opacity', 0));
235
+ if (!hasBarSeries) {
236
+ return;
178
237
  }
238
+ barSeries.forEach((s) => {
239
+ const sanitizedKey = sanitizeForCSS(s.dataKey);
240
+ svg.selectAll(`.bar-${sanitizedKey}`).style('opacity', 1);
241
+ });
242
+ };
243
+ const showTooltipAtIndex = (closestIndex) => {
179
244
  const dataPoint = data[closestIndex];
245
+ const dataPointPosition = dataPointPositions[closestIndex];
180
246
  lineSeries.forEach((s, i) => {
181
247
  const value = resolveSeriesValue(s, dataPoint, closestIndex);
182
248
  if (!Number.isFinite(value)) {
@@ -185,16 +251,15 @@ export class Tooltip {
185
251
  }
186
252
  if (isHorizontal) {
187
253
  focusCircles[i]
188
- .attr('cx', x(value))
254
+ .attr('cx', x(value) ?? 0)
189
255
  .attr('cy', dataPointPosition)
190
256
  .style('opacity', 1);
257
+ return;
191
258
  }
192
- else {
193
- focusCircles[i]
194
- .attr('cx', dataPointPosition)
195
- .attr('cy', y(value))
196
- .style('opacity', 1);
197
- }
259
+ focusCircles[i]
260
+ .attr('cx', dataPointPosition)
261
+ .attr('cy', y(value) ?? 0)
262
+ .style('opacity', 1);
198
263
  });
199
264
  if (hasBarSeries) {
200
265
  barSeries.forEach((s) => {
@@ -202,29 +267,9 @@ export class Tooltip {
202
267
  svg.selectAll(`.bar-${sanitizedKey}`).style('opacity', (_, i) => (i === closestIndex ? 1 : 0.5));
203
268
  });
204
269
  }
205
- let content;
206
- if (customFormatter) {
207
- content = customFormatter(dataPoint, series);
208
- }
209
- else {
210
- const labelValue = dataPoint[xKey];
211
- const label = labelFormatter
212
- ? labelFormatter(String(labelValue), dataPoint)
213
- : String(labelValue);
214
- content = `<strong>${label}</strong><br/>`;
215
- series.forEach((s) => {
216
- const value = dataPoint[s.dataKey];
217
- if (formatter) {
218
- content +=
219
- formatter(s.dataKey, value, dataPoint) +
220
- '<br/>';
221
- }
222
- else {
223
- content += `${s.dataKey}: ${value}<br/>`;
224
- }
225
- });
226
- }
227
- tooltip.style('visibility', 'visible').html(content);
270
+ tooltip
271
+ .style('visibility', 'visible')
272
+ .html(buildTooltipContent(dataPoint));
228
273
  const tooltipNode = tooltip.node();
229
274
  const tooltipRect = tooltipNode.getBoundingClientRect();
230
275
  const tooltipWidth = tooltipRect.width;
@@ -257,10 +302,7 @@ export class Tooltip {
257
302
  const maxY = y(minValue);
258
303
  const midY = (minY + maxY) / 2;
259
304
  tooltipX =
260
- svgRect.left +
261
- window.scrollX +
262
- dataPointPosition +
263
- offsetX;
305
+ svgRect.left + window.scrollX + dataPointPosition + offsetX;
264
306
  tooltipY =
265
307
  svgRect.top + window.scrollY + midY - tooltipHeight / 2;
266
308
  }
@@ -285,23 +327,146 @@ export class Tooltip {
285
327
  }
286
328
  }
287
329
  tooltipX = Math.max(10, tooltipX);
288
- tooltipY = Math.max(10, Math.min(tooltipY, window.innerHeight +
289
- window.scrollY -
290
- tooltipHeight -
291
- 10));
330
+ tooltipY = Math.max(10, Math.min(tooltipY, window.innerHeight + window.scrollY - tooltipHeight - 10));
292
331
  tooltip
293
332
  .style('left', `${tooltipX}px`)
294
333
  .style('top', `${tooltipY}px`);
334
+ };
335
+ const hideTooltip = () => {
336
+ tooltip.style('visibility', 'hidden');
337
+ clearVisualState();
338
+ };
339
+ const getFocusTargetBounds = (index) => {
340
+ if (isHorizontal) {
341
+ if (y.bandwidth) {
342
+ const targetHeight = y.bandwidth();
343
+ return {
344
+ x: plotArea.left,
345
+ y: dataPointPositions[index] - targetHeight / 2,
346
+ width: plotArea.width,
347
+ height: targetHeight,
348
+ };
349
+ }
350
+ const top = index === 0
351
+ ? plotArea.top
352
+ : (dataPointPositions[index - 1] +
353
+ dataPointPositions[index]) /
354
+ 2;
355
+ const bottom = index === dataPointPositions.length - 1
356
+ ? plotArea.bottom
357
+ : (dataPointPositions[index] +
358
+ dataPointPositions[index + 1]) /
359
+ 2;
360
+ return {
361
+ x: plotArea.left,
362
+ y: top,
363
+ width: plotArea.width,
364
+ height: bottom - top,
365
+ };
366
+ }
367
+ if (x.bandwidth) {
368
+ const targetWidth = x.bandwidth();
369
+ return {
370
+ x: dataPointPositions[index] - targetWidth / 2,
371
+ y: plotArea.top,
372
+ width: targetWidth,
373
+ height: plotArea.height,
374
+ };
375
+ }
376
+ const left = index === 0
377
+ ? plotArea.left
378
+ : (dataPointPositions[index - 1] +
379
+ dataPointPositions[index]) /
380
+ 2;
381
+ const right = index === dataPointPositions.length - 1
382
+ ? plotArea.right
383
+ : (dataPointPositions[index] +
384
+ dataPointPositions[index + 1]) /
385
+ 2;
386
+ return {
387
+ x: left,
388
+ y: plotArea.top,
389
+ width: right - left,
390
+ height: plotArea.height,
391
+ };
392
+ };
393
+ overlay
394
+ .on('mousemove', (event) => {
395
+ const [mouseX, mouseY] = pointer(event, svg.node());
396
+ showTooltipAtIndex(getClosestIndexFromPointer(mouseX, mouseY));
295
397
  })
296
398
  .on('mouseout', () => {
297
- tooltip.style('visibility', 'hidden');
298
- focusCircles.forEach((circle) => circle.style('opacity', 0));
299
- if (hasBarSeries) {
300
- barSeries.forEach((s) => {
301
- const sanitizedKey = sanitizeForCSS(s.dataKey);
302
- svg.selectAll(`.bar-${sanitizedKey}`).style('opacity', 1);
303
- });
399
+ if (isTooltipFocusTarget(document.activeElement)) {
400
+ return;
401
+ }
402
+ hideTooltip();
403
+ });
404
+ const focusTargets = svg
405
+ .append('g')
406
+ .attr('class', 'tooltip-focus-targets')
407
+ .selectAll('rect')
408
+ .data(data)
409
+ .join('rect')
410
+ .attr('class', 'tooltip-focus-target')
411
+ .attr('data-index', (_, i) => i)
412
+ .attr('x', (_, i) => getFocusTargetBounds(i).x)
413
+ .attr('y', (_, i) => getFocusTargetBounds(i).y)
414
+ .attr('width', (_, i) => getFocusTargetBounds(i).width)
415
+ .attr('height', (_, i) => getFocusTargetBounds(i).height)
416
+ .attr('tabindex', 0)
417
+ .attr('fill', 'transparent')
418
+ .attr('stroke', 'none')
419
+ .attr('stroke-width', 0)
420
+ .attr('aria-label', (dataPoint) => buildAccessibleLabel(dataPoint))
421
+ .style('pointer-events', 'none');
422
+ const focusTargetNodes = focusTargets.nodes();
423
+ focusTargets
424
+ .on('focus', function () {
425
+ const currentIndex = focusTargetNodes.indexOf(this);
426
+ if (currentIndex === -1) {
427
+ return;
428
+ }
429
+ select(this).attr('stroke', '#111827').attr('stroke-width', 2);
430
+ showTooltipAtIndex(currentIndex);
431
+ })
432
+ .on('blur', function (event) {
433
+ select(this).attr('stroke', 'none').attr('stroke-width', 0);
434
+ if (isTooltipFocusTarget(event.relatedTarget)) {
435
+ return;
436
+ }
437
+ hideTooltip();
438
+ })
439
+ .on('keydown', function (event) {
440
+ const currentIndex = focusTargetNodes.indexOf(this);
441
+ if (currentIndex === -1) {
442
+ return;
443
+ }
444
+ let nextIndex = null;
445
+ switch (event.key) {
446
+ case 'ArrowRight':
447
+ case 'ArrowDown':
448
+ nextIndex = currentIndex + 1;
449
+ break;
450
+ case 'ArrowLeft':
451
+ case 'ArrowUp':
452
+ nextIndex = currentIndex - 1;
453
+ break;
454
+ case 'Home':
455
+ nextIndex = 0;
456
+ break;
457
+ case 'End':
458
+ nextIndex = focusTargetNodes.length - 1;
459
+ break;
460
+ default:
461
+ return;
462
+ }
463
+ if (nextIndex === null ||
464
+ nextIndex < 0 ||
465
+ nextIndex >= focusTargetNodes.length) {
466
+ return;
304
467
  }
468
+ event.preventDefault();
469
+ focusTargetNodes[nextIndex].focus();
305
470
  });
306
471
  }
307
472
  cleanup() {
@@ -2,7 +2,7 @@ export type DeepPartial<T> = {
2
2
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
3
3
  };
4
4
  export type DataValue = string | number | boolean | Date | null | undefined;
5
- export type DataItem = Record<string, any>;
5
+ export type DataItem = Record<string, unknown>;
6
6
  export type GroupedDataGroup = {
7
7
  group: string;
8
8
  data: DataItem[];
@@ -37,8 +37,6 @@ export type ExportHooks<TConfig = Record<string, unknown>> = {
37
37
  };
38
38
  export type ColorPalette = string[];
39
39
  export type ChartTheme = {
40
- width: number;
41
- height: number;
42
40
  fontFamily: string;
43
41
  margins: {
44
42
  top: number;
@@ -53,11 +51,11 @@ export type ChartTheme = {
53
51
  colorPalette: ColorPalette;
54
52
  axis: {
55
53
  fontFamily: string;
56
- fontSize: string;
54
+ fontSize: number;
57
55
  fontWeight?: string;
58
56
  groupLabel?: {
59
57
  fontFamily?: string;
60
- fontSize?: string;
58
+ fontSize?: number;
61
59
  fontWeight?: string;
62
60
  color?: string;
63
61
  };
@@ -115,10 +113,22 @@ export type ChartTheme = {
115
113
  };
116
114
  };
117
115
  };
118
- export type ResponsiveBreakpoints = Record<string, number>;
116
+ export type ResolvedChartTheme = ChartTheme & {
117
+ width: number;
118
+ height: number;
119
+ };
120
+ export type ResponsiveBreakpointConfig = {
121
+ minWidth?: number;
122
+ maxWidth?: number;
123
+ theme?: DeepPartial<ChartTheme>;
124
+ components?: ResponsiveComponentOverride[];
125
+ };
126
+ export type ResponsiveBreakpointDefinition = number | ResponsiveBreakpointConfig;
127
+ export type ResponsiveBreakpoints = Record<string, ResponsiveBreakpointDefinition>;
119
128
  export type ResponsiveRenderContext = {
120
129
  width: number;
121
130
  height: number;
131
+ activeBreakpoints: string[];
122
132
  breakpoint: string | null;
123
133
  };
124
134
  export type ResponsiveComponentSnapshot = {
@@ -179,7 +189,6 @@ export type BarConfigBase = {
179
189
  dataKey: string;
180
190
  fill?: string;
181
191
  colorAdapter?: (data: DataItem, index: number) => string;
182
- orientation?: 'vertical' | 'horizontal';
183
192
  maxBarSize?: number;
184
193
  valueLabel?: BarValueLabelConfig;
185
194
  };
@@ -218,6 +227,8 @@ export declare function getSeriesColor(series: {
218
227
  fill?: string;
219
228
  }): string;
220
229
  export type LabelOversizedBehavior = 'truncate' | 'wrap' | 'hide';
230
+ export type AxisTickValue = string | number | Date;
231
+ export type AxisTickFormatter = (value: AxisTickValue) => string;
221
232
  export type XAxisConfigBase = {
222
233
  display?: boolean;
223
234
  dataKey?: string;
@@ -228,7 +239,7 @@ export type XAxisConfigBase = {
228
239
  rotatedLabels?: boolean;
229
240
  maxLabelWidth?: number;
230
241
  oversizedBehavior?: LabelOversizedBehavior;
231
- tickFormat?: string | ((value: number) => string) | null;
242
+ tickFormat?: string | AxisTickFormatter | null;
232
243
  autoHideOverlapping?: boolean;
233
244
  minLabelGap?: number;
234
245
  preserveEndLabels?: boolean;
@@ -238,7 +249,7 @@ export type XAxisConfig = XAxisConfigBase & {
238
249
  };
239
250
  export type YAxisConfigBase = {
240
251
  display?: boolean;
241
- tickFormat?: string | ((value: number) => string) | null;
252
+ tickFormat?: string | AxisTickFormatter | null;
242
253
  rotatedLabels?: boolean;
243
254
  maxLabelWidth?: number;
244
255
  oversizedBehavior?: LabelOversizedBehavior;
@@ -303,8 +314,14 @@ export type TitleConfig = TitleConfigBase & {
303
314
  exportHooks?: ExportHooks<TitleConfigBase>;
304
315
  };
305
316
  export type ScaleType = 'band' | 'linear' | 'time' | 'log';
306
- export type D3Scale = any;
307
317
  export type ScaleDomainValue = string | number | Date;
318
+ export type D3Scale = {
319
+ (value: ScaleDomainValue): number | undefined;
320
+ domain(): ScaleDomainValue[];
321
+ range(): number[];
322
+ copy(): D3Scale;
323
+ bandwidth?(): number;
324
+ };
308
325
  export type ScaleConfig = {
309
326
  type: ScaleType;
310
327
  domain?: ScaleDomainValue[];
@@ -1,8 +1,6 @@
1
- import { type ClassValue } from 'clsx';
2
1
  import type { DeepPartial } from './types.js';
3
2
  export { toChartData } from './grouped-tabular.js';
4
3
  export type { GroupedStringParseOptions, ToChartDataOptions, } from './grouped-tabular.js';
5
- export declare function cn(...inputs: ClassValue[]): string;
6
4
  /**
7
5
  * Sanitizes a string to be used as a CSS class name or ID.
8
6
  * Removes or replaces invalid characters for CSS selectors.
@@ -1,9 +1,4 @@
1
- import { clsx } from 'clsx';
2
- import { twMerge } from 'tailwind-merge';
3
1
  export { toChartData } from './grouped-tabular.js';
4
- export function cn(...inputs) {
5
- return twMerge(clsx(inputs));
6
- }
7
2
  /**
8
3
  * Sanitizes a string to be used as a CSS class name or ID.
9
4
  * Removes or replaces invalid characters for CSS selectors.
@@ -12,7 +12,7 @@ export type WordCloudConfig = {
12
12
  rotation?: WordCloudRotationMode;
13
13
  spiral?: WordCloudSpiral;
14
14
  };
15
- export type WordCloudChartConfig = Pick<BaseChartConfig, 'data' | 'theme' | 'responsive'> & {
15
+ export type WordCloudChartConfig = Pick<BaseChartConfig, 'data' | 'width' | 'height' | 'theme' | 'responsive'> & {
16
16
  wordCloud?: WordCloudConfig;
17
17
  };
18
18
  export declare class WordCloudChart extends BaseChart {
@@ -115,6 +115,8 @@ export class WordCloudChart extends BaseChart {
115
115
  createExportChart() {
116
116
  return new WordCloudChart({
117
117
  data: this.sourceData,
118
+ width: this.configuredWidth,
119
+ height: this.configuredHeight,
118
120
  theme: this.theme,
119
121
  responsive: this.responsiveConfig,
120
122
  wordCloud: this.options,
@@ -22,10 +22,11 @@ export declare class XAxis implements LayoutAwareComponent<XAxisConfigBase> {
22
22
  private readonly minLabelGap;
23
23
  private readonly preserveEndLabels;
24
24
  readonly exportHooks?: ExportHooks<XAxisConfigBase>;
25
+ private resolveFontSizeValue;
25
26
  private resolveGroupLabelStyle;
26
27
  constructor(config?: XAxisConfig);
27
28
  getExportConfig(): XAxisConfigBase;
28
- createExportComponent(override?: Partial<XAxisConfigBase>): LayoutAwareComponent;
29
+ createExportComponent(override?: Partial<XAxisConfigBase>): LayoutAwareComponent<XAxisConfigBase>;
29
30
  /**
30
31
  * Returns the space required by the x-axis
31
32
  */
@@ -2,14 +2,23 @@ import { axisBottom } from 'd3';
2
2
  import { measureTextWidth, truncateText, wrapText, mergeDeep } from './utils.js';
3
3
  import { GROUPED_CATEGORY_ID_KEY, GROUPED_CATEGORY_LABEL_KEY, GROUPED_GAP_TICK_PREFIX, GROUPED_GROUP_LABEL_KEY, } from './grouped-data.js';
4
4
  export class XAxis {
5
+ resolveFontSizeValue(fontSize, fallback) {
6
+ if (typeof fontSize === 'number' && Number.isFinite(fontSize)) {
7
+ return fontSize;
8
+ }
9
+ if (typeof fontSize === 'string') {
10
+ const parsedFontSize = parseFloat(fontSize);
11
+ if (Number.isFinite(parsedFontSize)) {
12
+ return parsedFontSize;
13
+ }
14
+ }
15
+ return fallback;
16
+ }
5
17
  resolveGroupLabelStyle(theme) {
6
18
  const axisGroupLabel = theme.axis.groupLabel;
7
19
  const fontFamily = axisGroupLabel?.fontFamily ?? theme.axis.fontFamily;
8
20
  const fontWeight = axisGroupLabel?.fontWeight ?? '700';
9
- const parsedFontSize = parseFloat(axisGroupLabel?.fontSize ?? theme.axis.fontSize);
10
- const fontSize = Number.isFinite(parsedFontSize)
11
- ? parsedFontSize
12
- : this.fontSize;
21
+ const fontSize = this.resolveFontSizeValue(axisGroupLabel?.fontSize ?? theme.axis.fontSize, this.fontSize);
13
22
  const color = axisGroupLabel?.color ?? '#111827';
14
23
  return {
15
24
  fontFamily,
@@ -91,7 +100,6 @@ export class XAxis {
91
100
  writable: true,
92
101
  value: void 0
93
102
  });
94
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
103
  Object.defineProperty(this, "tickFormat", {
96
104
  enumerable: true,
97
105
  configurable: true,
@@ -223,12 +231,7 @@ export class XAxis {
223
231
  this.wrapLineCount = 1;
224
232
  return;
225
233
  }
226
- const parsedFontSize = typeof theme.axis.fontSize === 'string'
227
- ? parseFloat(theme.axis.fontSize)
228
- : theme.axis.fontSize;
229
- this.fontSize = Number.isFinite(parsedFontSize)
230
- ? parsedFontSize
231
- : this.fontSize;
234
+ this.fontSize = this.resolveFontSizeValue(theme.axis.fontSize, this.fontSize);
232
235
  const fontSize = this.fontSize;
233
236
  const fontFamily = theme.axis.fontFamily;
234
237
  const fontWeight = theme.axis.fontWeight || 'normal';
@@ -308,11 +311,12 @@ export class XAxis {
308
311
  }
309
312
  else if (this.tickFormat) {
310
313
  // Apply tick formatting if specified
311
- if (typeof this.tickFormat === 'function') {
312
- axisGenerator.tickFormat(this.tickFormat);
314
+ const tickFormat = this.tickFormat;
315
+ if (typeof tickFormat === 'function') {
316
+ axisGenerator.tickFormat((value) => tickFormat(value));
313
317
  }
314
318
  else {
315
- axisGenerator.ticks(5, this.tickFormat);
319
+ axisGenerator.ticks(5, tickFormat);
316
320
  }
317
321
  }
318
322
  const axis = svg
@@ -1,7 +1,8 @@
1
1
  import { BaseChart, type BaseChartConfig, type BaseLayoutContext, type BaseRenderContext } from './base-chart.js';
2
- import type { ChartComponent } from './chart-interface.js';
3
- import { type AreaStackConfig, type BarStackConfig, type LegendSeries } from './types.js';
2
+ import type { ChartComponentBase } from './chart-interface.js';
3
+ import { type AreaStackConfig, type BarStackConfig, type LegendSeries, type Orientation } from './types.js';
4
4
  export type XYChartConfig = BaseChartConfig & {
5
+ orientation?: Orientation;
5
6
  barStack?: BarStackConfig;
6
7
  areaStack?: AreaStackConfig;
7
8
  };
@@ -11,11 +12,12 @@ export declare class XYChart extends BaseChart {
11
12
  private barStackGap;
12
13
  private barStackReverseSeries;
13
14
  private areaStackMode;
15
+ private readonly orientation;
14
16
  constructor(config: XYChartConfig);
15
- addChild(component: ChartComponent): this;
16
- protected getExportComponents(): ChartComponent[];
17
+ addChild(component: ChartComponentBase): this;
18
+ protected getExportComponents(): ChartComponentBase[];
17
19
  protected createExportChart(): BaseChart;
18
- protected applyComponentOverrides(overrides: Map<ChartComponent, ChartComponent>): () => void;
20
+ protected applyComponentOverrides(overrides: Map<ChartComponentBase, ChartComponentBase>): () => void;
19
21
  protected prepareLayout(context: BaseLayoutContext): void;
20
22
  protected renderChart({ svg, plotGroup, plotArea, }: BaseRenderContext): void;
21
23
  private getXKey;
@@ -28,6 +30,7 @@ export declare class XYChart extends BaseChart {
28
30
  private cloneSeriesWithOverride;
29
31
  private setupScales;
30
32
  private isHorizontalOrientation;
33
+ private validateSeriesOrientation;
31
34
  private collectSeriesValues;
32
35
  private getStackedAreaGroups;
33
36
  private buildBandDomainWithGroupGaps;