@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/README.md +5 -1
- package/dist/theme.js +6 -0
- package/dist/tooltip/dom.d.ts +60 -0
- package/dist/tooltip/dom.js +457 -0
- package/dist/tooltip/geometry.d.ts +19 -0
- package/dist/tooltip/geometry.js +857 -0
- package/dist/tooltip/types.d.ts +79 -0
- package/dist/tooltip/types.js +24 -0
- package/dist/tooltip/xy-interaction.d.ts +34 -0
- package/dist/tooltip/xy-interaction.js +791 -0
- package/dist/tooltip.d.ts +6 -75
- package/dist/tooltip.js +51 -1445
- package/dist/types.d.ts +7 -3
- package/dist/x-axis.d.ts +11 -1
- package/dist/x-axis.js +150 -10
- package/dist/xy-chart.d.ts +1 -0
- package/dist/xy-chart.js +10 -4
- package/docs/components.md +29 -22
- package/docs/xy-chart.md +3 -10
- package/package.json +1 -1
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 +=
|
|
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
|
-
|
|
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 =
|
|
501
|
+
const lineHeight = fontSize * 1.2;
|
|
429
502
|
// Update wrap line count for height calculation
|
|
430
|
-
|
|
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 +
|
|
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.
|
|
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')
|
package/dist/xy-chart.d.ts
CHANGED
|
@@ -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 [];
|
package/docs/components.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
518
|
-
|
|
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