@internetstiftelsen/charts 0.15.0 → 0.17.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/dist/types.d.ts CHANGED
@@ -269,6 +269,8 @@ export type XAxisConfigBase = {
269
269
  groupLabelKey?: string;
270
270
  showGroupLabels?: boolean;
271
271
  groupLabelGap?: number;
272
+ groupLabelMaxWidth?: number;
273
+ groupLabelOversizedBehavior?: LabelOversizedBehavior;
272
274
  rotatedLabels?: boolean;
273
275
  maxLabelWidth?: number;
274
276
  oversizedBehavior?: LabelOversizedBehavior;
@@ -297,9 +299,10 @@ export type GridConfigBase = {
297
299
  export type GridConfig = GridConfigBase & {
298
300
  exportHooks?: ExportHooks<GridConfigBase>;
299
301
  };
300
- export type TooltipMode = 'shared' | 'split';
301
- export type TooltipPosition = 'side' | 'vertical';
302
- export type TooltipBarAnchorPosition = 'top' | 'middle';
302
+ export type TooltipMode = 'shared' | 'split' | 'single';
303
+ export type TooltipPosition = 'auto' | 'side' | 'vertical';
304
+ export type TooltipBarAnchorPosition = 'auto' | 'top' | 'middle';
305
+ export type TooltipColorMode = 'theme' | 'series';
303
306
  export type TooltipTransitionConfig = {
304
307
  show?: boolean;
305
308
  duration?: number;
@@ -309,6 +312,7 @@ export type TooltipConfigBase = {
309
312
  mode?: TooltipMode;
310
313
  position?: TooltipPosition;
311
314
  barAnchorPosition?: TooltipBarAnchorPosition;
315
+ colorMode?: TooltipColorMode;
312
316
  maxWidth?: number;
313
317
  transition?: TooltipTransitionConfig;
314
318
  formatter?: SeriesValueFormatter;
package/dist/x-axis.d.ts CHANGED
@@ -9,6 +9,8 @@ export declare class XAxis implements LayoutAwareComponent<XAxisConfigBase> {
9
9
  readonly groupLabelKey?: string;
10
10
  readonly showGroupLabels: boolean;
11
11
  readonly groupLabelGap: number;
12
+ private readonly groupLabelMaxWidth?;
13
+ private readonly groupLabelOversizedBehavior;
12
14
  private readonly rotatedLabels;
13
15
  private readonly tickPadding;
14
16
  private fontSize;
@@ -16,6 +18,7 @@ export declare class XAxis implements LayoutAwareComponent<XAxisConfigBase> {
16
18
  private readonly oversizedBehavior;
17
19
  private readonly tickFormat;
18
20
  private wrapLineCount;
21
+ private groupLabelWrapLineCount;
19
22
  private estimatedHeight;
20
23
  private estimatedTickLabelVerticalFootprint;
21
24
  private readonly autoHideOverlapping;
@@ -31,7 +34,7 @@ export declare class XAxis implements LayoutAwareComponent<XAxisConfigBase> {
31
34
  * Returns the space required by the x-axis
32
35
  */
33
36
  getRequiredSpace(): ComponentSpace;
34
- estimateLayoutSpace(labels: unknown[], theme: ChartTheme, svg: SVGSVGElement): void;
37
+ estimateLayoutSpace(labels: unknown[], theme: ChartTheme, svg: SVGSVGElement, data?: DataItem[], estimatedXAxisRangeWidth?: number): void;
35
38
  clearEstimatedSpace(): void;
36
39
  private getTickLabelVerticalFootprint;
37
40
  render(svg: Selection<SVGSVGElement, undefined, null, undefined>, x: D3Scale, theme: ChartTheme, yPosition: number, data?: DataItem[]): void;
@@ -39,15 +42,22 @@ export declare class XAxis implements LayoutAwareComponent<XAxisConfigBase> {
39
42
  private renderGroupLabels;
40
43
  private buildGroupRanges;
41
44
  private applyLabelConstraints;
45
+ private applyGroupLabelConstraints;
46
+ private resolveGroupLabelMaxWidth;
42
47
  private wrapTextElement;
43
48
  private addTitleTooltip;
44
49
  private applyAutoHiding;
45
50
  private measureLabel;
51
+ private estimateGroupLabelWrapLineCount;
52
+ private buildGroupLabelEstimates;
53
+ private resolveEstimatedGroupLabelMaxWidth;
54
+ private getLabelBlockHeight;
46
55
  private setEstimatedDimensions;
47
56
  private createAxisGenerator;
48
57
  private applyAxisTextConstraints;
49
58
  private applyLabelRotation;
50
59
  private resolveGroupRangeInput;
60
+ private resolveGroupLabelKey;
51
61
  private collectAutoHideLabels;
52
62
  private measureMaxAutoHideLabelWidth;
53
63
  private applyAutoHideVisibility;
package/dist/x-axis.js CHANGED
@@ -1,10 +1,12 @@
1
1
  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
+ const GROUP_LABEL_HORIZONTAL_PADDING = 4;
4
5
  const DEFAULT_X_AXIS_CONFIG = {
5
6
  display: true,
6
7
  showGroupLabels: false,
7
8
  groupLabelGap: 10,
9
+ groupLabelOversizedBehavior: 'truncate',
8
10
  rotatedLabels: false,
9
11
  oversizedBehavior: 'truncate',
10
12
  tickFormat: null,
@@ -81,6 +83,18 @@ export class XAxis {
81
83
  writable: true,
82
84
  value: void 0
83
85
  });
86
+ Object.defineProperty(this, "groupLabelMaxWidth", {
87
+ enumerable: true,
88
+ configurable: true,
89
+ writable: true,
90
+ value: void 0
91
+ });
92
+ Object.defineProperty(this, "groupLabelOversizedBehavior", {
93
+ enumerable: true,
94
+ configurable: true,
95
+ writable: true,
96
+ value: void 0
97
+ });
84
98
  Object.defineProperty(this, "rotatedLabels", {
85
99
  enumerable: true,
86
100
  configurable: true,
@@ -123,6 +137,12 @@ export class XAxis {
123
137
  writable: true,
124
138
  value: 1
125
139
  });
140
+ Object.defineProperty(this, "groupLabelWrapLineCount", {
141
+ enumerable: true,
142
+ configurable: true,
143
+ writable: true,
144
+ value: 1
145
+ });
126
146
  Object.defineProperty(this, "estimatedHeight", {
127
147
  enumerable: true,
128
148
  configurable: true,
@@ -169,6 +189,9 @@ export class XAxis {
169
189
  this.groupLabelKey = resolvedConfig.groupLabelKey;
170
190
  this.showGroupLabels = resolvedConfig.showGroupLabels;
171
191
  this.groupLabelGap = resolvedConfig.groupLabelGap;
192
+ this.groupLabelMaxWidth = resolvedConfig.groupLabelMaxWidth;
193
+ this.groupLabelOversizedBehavior =
194
+ resolvedConfig.groupLabelOversizedBehavior;
172
195
  this.rotatedLabels = resolvedConfig.rotatedLabels;
173
196
  this.maxLabelWidth = resolvedConfig.maxLabelWidth;
174
197
  this.oversizedBehavior = resolvedConfig.oversizedBehavior;
@@ -186,6 +209,8 @@ export class XAxis {
186
209
  groupLabelKey: this.groupLabelKey,
187
210
  showGroupLabels: this.showGroupLabels,
188
211
  groupLabelGap: this.groupLabelGap,
212
+ groupLabelMaxWidth: this.groupLabelMaxWidth,
213
+ groupLabelOversizedBehavior: this.groupLabelOversizedBehavior,
189
214
  rotatedLabels: this.rotatedLabels,
190
215
  maxLabelWidth: this.maxLabelWidth,
191
216
  oversizedBehavior: this.oversizedBehavior,
@@ -231,7 +256,10 @@ export class XAxis {
231
256
  height += (this.wrapLineCount - 1) * this.fontSize * 1.2;
232
257
  }
233
258
  if (this.showGroupLabels) {
234
- height += this.groupLabelGap + this.fontSize + 5;
259
+ height +=
260
+ this.groupLabelGap +
261
+ this.getLabelBlockHeight(this.fontSize, this.groupLabelWrapLineCount) +
262
+ 5;
235
263
  }
236
264
  return {
237
265
  width: 0, // X-axis spans full width
@@ -239,11 +267,12 @@ export class XAxis {
239
267
  position: 'bottom',
240
268
  };
241
269
  }
242
- estimateLayoutSpace(labels, theme, svg) {
270
+ estimateLayoutSpace(labels, theme, svg, data = [], estimatedXAxisRangeWidth) {
243
271
  if (!labels.length) {
244
272
  this.estimatedHeight = null;
245
273
  this.estimatedTickLabelVerticalFootprint = null;
246
274
  this.wrapLineCount = 1;
275
+ this.groupLabelWrapLineCount = 1;
247
276
  return;
248
277
  }
249
278
  this.fontSize = this.resolveFontSizeValue(theme.axis.fontSize, this.fontSize);
@@ -261,12 +290,14 @@ export class XAxis {
261
290
  maxLines = Math.max(maxLines, measurement.lines);
262
291
  maxWidth = Math.max(maxWidth, measurement.width);
263
292
  }
293
+ this.groupLabelWrapLineCount = this.estimateGroupLabelWrapLineCount(data, theme, svg, estimatedXAxisRangeWidth);
264
294
  this.setEstimatedDimensions(maxWidth, maxLines, theme);
265
295
  this.wrapLineCount = maxLines;
266
296
  }
267
297
  clearEstimatedSpace() {
268
298
  this.estimatedHeight = null;
269
299
  this.estimatedTickLabelVerticalFootprint = null;
300
+ this.groupLabelWrapLineCount = 1;
270
301
  }
271
302
  getTickLabelVerticalFootprint() {
272
303
  if (this.estimatedTickLabelVerticalFootprint !== null) {
@@ -345,6 +376,7 @@ export class XAxis {
345
376
  .attr('font-weight', groupLabelStyle.fontWeight)
346
377
  .attr('fill', groupLabelStyle.color)
347
378
  .text((range) => range.label);
379
+ this.applyGroupLabelConstraints(groupLayer.selectAll('text'), svg.node(), groupLabelStyle);
348
380
  }
349
381
  buildGroupRanges(scale, data) {
350
382
  const input = this.resolveGroupRangeInput(scale, data);
@@ -422,12 +454,55 @@ export class XAxis {
422
454
  }
423
455
  });
424
456
  }
425
- wrapTextElement(textEl, lines, originalText) {
457
+ applyGroupLabelConstraints(groupLabels, svg, groupLabelStyle) {
458
+ groupLabels.each((range, i, nodes) => {
459
+ const textEl = nodes[i];
460
+ const maxWidth = this.resolveGroupLabelMaxWidth(range);
461
+ if (maxWidth <= 0) {
462
+ textEl.style.visibility = 'hidden';
463
+ return;
464
+ }
465
+ const originalText = textEl.textContent || '';
466
+ const textWidth = measureTextWidth(originalText, groupLabelStyle.fontSize, groupLabelStyle.fontFamily, groupLabelStyle.fontWeight, svg);
467
+ if (textWidth <= maxWidth) {
468
+ return;
469
+ }
470
+ switch (this.groupLabelOversizedBehavior) {
471
+ case 'truncate': {
472
+ const result = truncateText(originalText, maxWidth, groupLabelStyle.fontSize, groupLabelStyle.fontFamily, groupLabelStyle.fontWeight, svg);
473
+ textEl.textContent = result.text;
474
+ if (result.truncated) {
475
+ this.addTitleTooltip(textEl, originalText);
476
+ }
477
+ break;
478
+ }
479
+ case 'wrap': {
480
+ const lines = wrapText(originalText, maxWidth, groupLabelStyle.fontSize, groupLabelStyle.fontFamily, groupLabelStyle.fontWeight, svg);
481
+ this.wrapTextElement(textEl, lines, originalText, groupLabelStyle.fontSize, false);
482
+ break;
483
+ }
484
+ case 'hide': {
485
+ textEl.style.visibility = 'hidden';
486
+ break;
487
+ }
488
+ }
489
+ });
490
+ }
491
+ resolveGroupLabelMaxWidth(range) {
492
+ if (this.groupLabelMaxWidth !== undefined) {
493
+ return this.groupLabelMaxWidth;
494
+ }
495
+ const rangeWidth = Math.abs(range.end - range.start);
496
+ return Math.max(0, rangeWidth - GROUP_LABEL_HORIZONTAL_PADDING * 2);
497
+ }
498
+ wrapTextElement(textEl, lines, originalText, fontSize = this.fontSize, updateWrapLineCount = true) {
426
499
  // Clear existing content
427
500
  textEl.textContent = '';
428
- const lineHeight = this.fontSize * 1.2;
501
+ const lineHeight = fontSize * 1.2;
429
502
  // Update wrap line count for height calculation
430
- this.wrapLineCount = Math.max(this.wrapLineCount, lines.length);
503
+ if (updateWrapLineCount) {
504
+ this.wrapLineCount = Math.max(this.wrapLineCount, lines.length);
505
+ }
431
506
  lines.forEach((line, i) => {
432
507
  const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
433
508
  tspan.textContent = line;
@@ -482,6 +557,66 @@ export class XAxis {
482
557
  lines: 1,
483
558
  };
484
559
  }
560
+ estimateGroupLabelWrapLineCount(data, theme, svg, estimatedXAxisRangeWidth) {
561
+ if (!this.showGroupLabels ||
562
+ this.groupLabelOversizedBehavior !== 'wrap') {
563
+ return 1;
564
+ }
565
+ const groupLabels = this.buildGroupLabelEstimates(data);
566
+ if (groupLabels.length === 0) {
567
+ return 1;
568
+ }
569
+ const groupLabelStyle = this.resolveGroupLabelStyle(theme);
570
+ const totalItemCount = data.length;
571
+ return groupLabels.reduce((maxLines, groupLabel) => {
572
+ const maxWidth = this.groupLabelMaxWidth ??
573
+ this.resolveEstimatedGroupLabelMaxWidth(groupLabel.itemCount, totalItemCount, estimatedXAxisRangeWidth);
574
+ if (maxWidth === undefined || maxWidth <= 0) {
575
+ return maxLines;
576
+ }
577
+ const lines = wrapText(groupLabel.label, maxWidth, groupLabelStyle.fontSize, groupLabelStyle.fontFamily, groupLabelStyle.fontWeight, svg);
578
+ return Math.max(maxLines, lines.length || 1);
579
+ }, 1);
580
+ }
581
+ buildGroupLabelEstimates(data) {
582
+ const groupLabelKey = this.resolveGroupLabelKey();
583
+ if (!this.dataKey || !groupLabelKey || data.length === 0) {
584
+ return [];
585
+ }
586
+ const labels = [];
587
+ let previousLabel = null;
588
+ let currentEstimate = null;
589
+ data.forEach((row) => {
590
+ const label = String(row[groupLabelKey] ?? '');
591
+ if (label === previousLabel) {
592
+ if (currentEstimate) {
593
+ currentEstimate.itemCount += 1;
594
+ }
595
+ return;
596
+ }
597
+ previousLabel = label;
598
+ if (label.trim() !== '') {
599
+ currentEstimate = { label, itemCount: 1 };
600
+ labels.push(currentEstimate);
601
+ return;
602
+ }
603
+ currentEstimate = null;
604
+ });
605
+ return labels;
606
+ }
607
+ resolveEstimatedGroupLabelMaxWidth(itemCount, totalItemCount, estimatedXAxisRangeWidth) {
608
+ if (estimatedXAxisRangeWidth === undefined ||
609
+ totalItemCount <= 0 ||
610
+ itemCount <= 0) {
611
+ return undefined;
612
+ }
613
+ const groupWidth = (estimatedXAxisRangeWidth * itemCount) / totalItemCount;
614
+ return Math.max(0, groupWidth - GROUP_LABEL_HORIZONTAL_PADDING * 2);
615
+ }
616
+ getLabelBlockHeight(fontSize, lineCount) {
617
+ const lineHeight = fontSize * 1.2;
618
+ return fontSize + Math.max(0, lineCount - 1) * lineHeight;
619
+ }
485
620
  setEstimatedDimensions(maxWidth, maxLines, theme) {
486
621
  const lineHeight = this.fontSize * 1.2;
487
622
  const textHeight = lineHeight * maxLines;
@@ -500,7 +635,9 @@ export class XAxis {
500
635
  if (this.showGroupLabels) {
501
636
  const groupLabelStyle = this.resolveGroupLabelStyle(theme);
502
637
  this.estimatedHeight +=
503
- this.groupLabelGap + groupLabelStyle.fontSize + 5;
638
+ this.groupLabelGap +
639
+ this.getLabelBlockHeight(groupLabelStyle.fontSize, this.groupLabelWrapLineCount) +
640
+ 5;
504
641
  }
505
642
  }
506
643
  createAxisGenerator(x, labelLookup) {
@@ -548,10 +685,7 @@ export class XAxis {
548
685
  typeof scale.bandwidth !== 'function') {
549
686
  return null;
550
687
  }
551
- const groupLabelKey = this.groupLabelKey ??
552
- (this.dataKey === GROUPED_CATEGORY_ID_KEY
553
- ? GROUPED_GROUP_LABEL_KEY
554
- : undefined);
688
+ const groupLabelKey = this.resolveGroupLabelKey();
555
689
  if (!groupLabelKey) {
556
690
  return null;
557
691
  }
@@ -570,6 +704,12 @@ export class XAxis {
570
704
  groupLookup,
571
705
  };
572
706
  }
707
+ resolveGroupLabelKey() {
708
+ return (this.groupLabelKey ??
709
+ (this.dataKey === GROUPED_CATEGORY_ID_KEY
710
+ ? GROUPED_GROUP_LABEL_KEY
711
+ : undefined));
712
+ }
573
713
  collectAutoHideLabels(axisGroup) {
574
714
  return axisGroup
575
715
  .selectAll('.tick')
@@ -26,6 +26,7 @@ export declare class XYChart extends BaseChart {
26
26
  update(data: ChartData): void;
27
27
  protected applyComponentOverrides(overrides: Map<ChartComponentBase, ChartComponentBase>): () => void;
28
28
  protected prepareLayout(context: BaseLayoutContext): void;
29
+ private getEstimatedXAxisRangeWidth;
29
30
  private getYAxisEstimateLabels;
30
31
  private createContinuousScaleForLayoutEstimate;
31
32
  protected renderChart({ svg, plotGroup, plotArea, }: BaseRenderContext): void;
package/dist/xy-chart.js CHANGED
@@ -155,6 +155,9 @@ export class XYChart extends BaseChart {
155
155
  super.prepareLayout(context);
156
156
  this.xAxis?.clearEstimatedSpace?.();
157
157
  this.yAxis?.clearEstimatedSpace?.();
158
+ if (this.yAxis) {
159
+ this.yAxis.estimateLayoutSpace?.(this.getYAxisEstimateLabels(), this.renderTheme, context.svgNode);
160
+ }
158
161
  if (this.xAxis) {
159
162
  const xKey = this.getXKey();
160
163
  const labelKey = this.xAxis.labelKey;
@@ -164,12 +167,15 @@ export class XYChart extends BaseChart {
164
167
  }
165
168
  return item[xKey];
166
169
  });
167
- this.xAxis.estimateLayoutSpace?.(labels, this.renderTheme, context.svgNode);
168
- }
169
- if (this.yAxis) {
170
- this.yAxis.estimateLayoutSpace?.(this.getYAxisEstimateLabels(), this.renderTheme, context.svgNode);
170
+ this.xAxis.estimateLayoutSpace?.(labels, this.renderTheme, context.svgNode, this.data, this.getEstimatedXAxisRangeWidth());
171
171
  }
172
172
  }
173
+ getEstimatedXAxisRangeWidth() {
174
+ const yAxisWidth = this.yAxis?.getRequiredSpace().width ?? 0;
175
+ const { left, right } = this.renderTheme.margins;
176
+ const xScalePadding = 10;
177
+ return Math.max(0, this.width - left - right - yAxisWidth - xScalePadding);
178
+ }
173
179
  getYAxisEstimateLabels() {
174
180
  if (!this.yAxis) {
175
181
  return [];
@@ -14,6 +14,8 @@ new XAxis({
14
14
  groupLabelKey?: string, // Optional key used for second-row grouped labels
15
15
  showGroupLabels?: boolean, // Show second-row grouped labels (default: false)
16
16
  groupLabelGap?: number, // Vertical gap between tick row and grouped row
17
+ groupLabelMaxWidth?: number, // Optional cap for grouped-label width
18
+ groupLabelOversizedBehavior?: 'truncate' | 'wrap' | 'hide', // Defaults to truncate
17
19
  rotatedLabels?: boolean, // Rotate tick labels -45deg
18
20
  maxLabelWidth?: number, // Optional cap for tick-label width
19
21
  oversizedBehavior?: 'truncate' | 'wrap' | 'hide', // Only applies when maxLabelWidth is set
@@ -24,7 +26,11 @@ new XAxis({
24
26
  })
25
27
  ```
26
28
 
27
- Grouped label styles come from `theme.axis.groupLabel` and are bold by default.
29
+ Grouped label styles come from `theme.axis.groupLabel` and are bold by
30
+ default. When `groupLabelMaxWidth` is omitted, grouped labels are automatically
31
+ capped to their rendered group range so adjacent group labels do not collide.
32
+ Use `groupLabelOversizedBehavior` to truncate, wrap, or hide labels that exceed
33
+ that cap.
28
34
 
29
35
  ### Example
30
36
 
@@ -119,9 +125,10 @@ Renders interactive tooltips on hover and keyboard focus.
119
125
 
120
126
  ```typescript
121
127
  new Tooltip({
122
- mode?: 'shared' | 'split',
123
- position?: 'side' | 'vertical',
124
- barAnchorPosition?: 'top' | 'middle',
128
+ mode?: 'shared' | 'split' | 'single',
129
+ position?: 'auto' | 'side' | 'vertical',
130
+ barAnchorPosition?: 'auto' | 'top' | 'middle',
131
+ colorMode?: 'theme' | 'series',
125
132
  maxWidth?: number, // default: 280
126
133
  transition?: {
127
134
  show?: boolean,
@@ -140,30 +147,30 @@ The formatter receives:
140
147
 
141
148
  Tooltip modes:
142
149
 
143
- - `shared` - One tooltip per hovered category/value group
144
150
  - `split` - Default. One tooltip per visible series at the hovered category/value group
151
+ - `single` - One tooltip for the closest visible series at the hovered category/value group
152
+ - `shared` - One grouped tooltip per hovered category/value group
145
153
 
146
- Split tooltip positions:
154
+ Single and shared tooltips omit series whose value is `null` or `undefined` for
155
+ the hovered data point. `customFormatter` receives the current series in
156
+ `split` and `single` modes, and the visible series list in `shared` mode.
147
157
 
148
- - `side` - Default. Place split tooltips to the side of each data point
149
- - `vertical` - Place split tooltips above or below each data point
158
+ Use `position: 'auto' | 'side' | 'vertical'` for XY tooltip placement.
159
+ `auto` is the default. It uses above/below placement for horizontal XY charts
160
+ and side placement elsewhere. For bar tooltips, `barAnchorPosition` defaults to
161
+ `auto`, which aims arrows inside the visible bar segment when possible.
150
162
 
151
- When `split` mode is active, `customFormatter` receives the current series only for
152
- that tooltip box instead of the full visible series list. Split tooltips include
153
- directional arrows and avoid overlapping on the same side of the chart when
154
- possible.
163
+ For horizontal bar charts, prefer `position: 'auto'` or `position: 'vertical'`.
164
+ `position: 'side'` and `barAnchorPosition: 'top' | 'middle'` are kept for
165
+ legacy configs, but horizontal bars resolve bar anchoring automatically.
155
166
 
156
- For bars, `barAnchorPosition` controls whether split tooltips point to the `top`
157
- or `middle` of each bar.
167
+ Tooltips default to a `280px` max width. Set `transition.show: true` to fade and
168
+ slide tooltips between hovered positions. Tooltip colors use `theme.tooltip` by
169
+ default. Set `colorMode: 'series'` to color-code XY tooltips from the series
170
+ color, with matching background and border colors plus automatic contrast text.
158
171
 
159
- Tooltips default to a `280px` max width so longer content wraps. Set `maxWidth`
160
- to choose a different cap in pixels.
161
-
162
- Set `transition.show: true` to fade tooltips in and out. Tooltip position and
163
- connector geometry update immediately; only opacity and the small entrance
164
- offset transition.
165
-
166
- Tooltip box, text, connector, and arrow colors use `theme.tooltip`.
172
+ `defaultResponsiveConfig` switches tooltip components to `mode: 'shared'` at
173
+ the `sm` breakpoint so compact XY charts use one grouped tooltip by default.
167
174
 
168
175
  Formatter, label formatter, and custom formatter output is inserted as HTML.
169
176
  Only return trusted content or sanitize user-provided strings before returning
package/docs/xy-chart.md CHANGED
@@ -78,6 +78,7 @@ Rules for grouped datasets:
78
78
  - Remaining keys are treated as metric columns.
79
79
  - Each `group` must be a non-empty string.
80
80
  - Group labels are rendered on a second x-axis row when `XAxis.showGroupLabels` is enabled.
81
+ - Group labels are capped to their rendered group range by default. Set `XAxis.groupLabelMaxWidth` and `XAxis.groupLabelOversizedBehavior` to override truncation, wrapping, or hiding.
81
82
  - CSV/XLSX exports use spreadsheet-style grouped rows (blank first two headers, blank continuation group cells, no spacer rows).
82
83
 
83
84
  ### Scale Options
@@ -514,16 +515,8 @@ Bar charts support different stacking modes:
514
515
  - `percent` - 100% stacked bars
515
516
  - `layer` - Overlapping bars
516
517
 
517
- XY charts use split tooltips by default, rendering one tooltip per visible
518
- series at the hovered category. Use
519
- `new Tooltip({ mode: 'shared' | 'split', position: 'side' | 'vertical' })`
520
- to override the grouping and split-tooltip placement.
521
- For bars, set `barAnchorPosition: 'top' | 'middle'` to choose whether split
522
- tooltips point to the top or middle of each bar.
523
- Tooltips default to a `280px` max width. Set `maxWidth` to choose a different
524
- cap in pixels.
525
- Use `transition: { show: true, duration: 120, easing: 'ease-out' }` to opt in
526
- to softer tooltip opacity transitions without delaying position updates.
518
+ Add `new Tooltip()` for hover and focus tooltips. XY charts use split tooltips
519
+ by default; see the [Tooltip component](./components.md#tooltip) for options.
527
520
 
528
521
  Use `barStack.reverseSeries: true` to reverse bar series display order for
529
522
  rendering, legend entries, and split tooltip ordering without changing data
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.15.0",
2
+ "version": "0.17.0",
3
3
  "name": "@internetstiftelsen/charts",
4
4
  "type": "module",
5
5
  "sideEffects": false,