@internetstiftelsen/charts 0.13.1 → 0.13.3
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 +2 -1
- package/dist/tooltip.d.ts +24 -0
- package/dist/tooltip.js +288 -18
- package/dist/types.d.ts +2 -0
- package/dist/xy-chart.d.ts +3 -0
- package/dist/xy-chart.js +48 -7
- package/docs/components.md +4 -0
- package/docs/xy-chart.md +8 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,8 @@ A framework-agnostic, composable charting library built on D3.js with TypeScript
|
|
|
13
13
|
- **Custom Value Labels** - XY, pie, and donut charts support optional on-chart labels with custom formatters
|
|
14
14
|
- **Optional XY Animation** - Animate XY series on first render and `chart.update(...)` with `animate`
|
|
15
15
|
- **Optional Gauge Animation** - Animate gauge value transitions with `gauge.animate`
|
|
16
|
-
- **Stacking Control** - Bar stacking modes with optional reversed visual series order
|
|
16
|
+
- **Stacking Control** - Bar and area stacking modes with optional reversed visual series order
|
|
17
|
+
- **Configurable Tooltips** - Shared or split tooltips with connectors, transitions, and default max-width wrapping
|
|
17
18
|
- **Axis Direction Control** - Use `scales.x.reverse` / `scales.y.reverse` to flip an axis when needed
|
|
18
19
|
- **Flexible Scales** - Band, linear, time, and logarithmic scales (bar value axes stay linear)
|
|
19
20
|
- **Explicit or Responsive Sizing** - Set top-level `width`/`height` or let the container drive size
|
package/dist/tooltip.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare class Tooltip implements ChartComponent<TooltipConfigBase> {
|
|
|
14
14
|
readonly mode: TooltipMode;
|
|
15
15
|
readonly position: TooltipPosition;
|
|
16
16
|
readonly barAnchorPosition: TooltipBarAnchorPosition;
|
|
17
|
+
readonly maxWidth: number;
|
|
17
18
|
readonly transition: Required<TooltipTransitionConfig>;
|
|
18
19
|
readonly formatter?: (dataKey: string, value: DataValue, data: DataItem) => string;
|
|
19
20
|
readonly labelFormatter?: (label: string, data: DataItem) => string;
|
|
@@ -68,6 +69,8 @@ export declare class Tooltip implements ChartComponent<TooltipConfigBase> {
|
|
|
68
69
|
private resolveTooltipConnectorLayout;
|
|
69
70
|
private resolveTooltipBoxArrowPosition;
|
|
70
71
|
private resolveTooltipConnectorPath;
|
|
72
|
+
private isTooltipArrowTipInsideHorizontalAnchorSpan;
|
|
73
|
+
private isTooltipArrowTipInsideVerticalAnchorSpan;
|
|
71
74
|
private resolveTooltipArrowTip;
|
|
72
75
|
private hasFiniteNumbers;
|
|
73
76
|
private resolveSplitTooltipCollisions;
|
|
@@ -75,7 +78,28 @@ export declare class Tooltip implements ChartComponent<TooltipConfigBase> {
|
|
|
75
78
|
private getOppositeVerticalArrowEdge;
|
|
76
79
|
private flipTooltipIfItReducesCollisions;
|
|
77
80
|
private countSplitTooltipCollisions;
|
|
81
|
+
private countPlacedLayoutsOnEdge;
|
|
78
82
|
private doSplitTooltipLayoutsOverlap;
|
|
79
83
|
private resolveSplitTooltipPositions;
|
|
84
|
+
private resolveHorizontalChartSplitTooltipPositions;
|
|
85
|
+
private resolveVerticalChartSplitTooltipPositions;
|
|
86
|
+
private getSplitTooltipViewportBounds;
|
|
87
|
+
private groupSplitTooltipLayoutsByEdge;
|
|
88
|
+
private resolveSideSplitTooltipPositions;
|
|
89
|
+
private resolveHorizontalSideSplitTooltipPositions;
|
|
90
|
+
private resolveHorizontalSideSplitTooltipCollisions;
|
|
91
|
+
private flipHorizontalSideTooltipIfItReducesCollisions;
|
|
92
|
+
private findReusableHorizontalTooltipLane;
|
|
93
|
+
private resolveNewHorizontalTooltipLaneTop;
|
|
94
|
+
private getHorizontalTooltipLaneTopCandidates;
|
|
95
|
+
private assignLayoutToHorizontalTooltipLane;
|
|
96
|
+
private doHorizontalTooltipLanesOverlap;
|
|
97
|
+
private doSplitTooltipLayoutsOverlapHorizontally;
|
|
98
|
+
private resolveHorizontalChartAboveBelowSplitTooltipPositions;
|
|
99
|
+
private resolvePackedAboveBelowTooltipRow;
|
|
100
|
+
private resolveVerticalChartAboveBelowSplitTooltipPositions;
|
|
101
|
+
private resolveCollisionAwareAboveBelowTooltipPositions;
|
|
102
|
+
private resolveNonOverlappingAboveBelowTooltipLeft;
|
|
103
|
+
private doesSplitTooltipOverlapPlacedLayouts;
|
|
80
104
|
}
|
|
81
105
|
export {};
|
package/dist/tooltip.js
CHANGED
|
@@ -16,6 +16,7 @@ const TOOLTIP_ARROW_FILL_Z_INDEX = 5;
|
|
|
16
16
|
const TOOLTIP_BODY_Z_INDEX = 6;
|
|
17
17
|
const TOOLTIP_TOTAL_BORDER_WIDTH_PX = TOOLTIP_BORDER_WIDTH_PX * 2;
|
|
18
18
|
const SPLIT_TOOLTIP_GAP_PX = 8;
|
|
19
|
+
const DEFAULT_TOOLTIP_MAX_WIDTH_PX = 280;
|
|
19
20
|
const DEFAULT_TOOLTIP_TRANSITION = {
|
|
20
21
|
show: false,
|
|
21
22
|
duration: 120,
|
|
@@ -55,6 +56,12 @@ export class Tooltip {
|
|
|
55
56
|
writable: true,
|
|
56
57
|
value: void 0
|
|
57
58
|
});
|
|
59
|
+
Object.defineProperty(this, "maxWidth", {
|
|
60
|
+
enumerable: true,
|
|
61
|
+
configurable: true,
|
|
62
|
+
writable: true,
|
|
63
|
+
value: void 0
|
|
64
|
+
});
|
|
58
65
|
Object.defineProperty(this, "transition", {
|
|
59
66
|
enumerable: true,
|
|
60
67
|
configurable: true,
|
|
@@ -109,13 +116,17 @@ export class Tooltip {
|
|
|
109
116
|
writable: true,
|
|
110
117
|
value: null
|
|
111
118
|
});
|
|
112
|
-
const { mode = 'split', position = 'side', barAnchorPosition = 'middle', transition, formatter, labelFormatter, customFormatter, exportHooks, } = config;
|
|
119
|
+
const { mode = 'split', position = 'side', barAnchorPosition = 'middle', maxWidth, transition, formatter, labelFormatter, customFormatter, exportHooks, } = config;
|
|
113
120
|
const tooltipId = Tooltip.nextTooltipId++;
|
|
114
121
|
this.id = `iisChartTooltip-${tooltipId}`;
|
|
115
122
|
this.splitTooltipOwner = `${this.id}-split`;
|
|
116
123
|
this.mode = mode;
|
|
117
124
|
this.position = position;
|
|
118
125
|
this.barAnchorPosition = barAnchorPosition;
|
|
126
|
+
this.maxWidth =
|
|
127
|
+
maxWidth !== undefined && Number.isFinite(maxWidth) && maxWidth > 0
|
|
128
|
+
? maxWidth
|
|
129
|
+
: DEFAULT_TOOLTIP_MAX_WIDTH_PX;
|
|
119
130
|
this.transition = {
|
|
120
131
|
...DEFAULT_TOOLTIP_TRANSITION,
|
|
121
132
|
...transition,
|
|
@@ -130,6 +141,7 @@ export class Tooltip {
|
|
|
130
141
|
mode: this.mode,
|
|
131
142
|
position: this.position,
|
|
132
143
|
barAnchorPosition: this.barAnchorPosition,
|
|
144
|
+
maxWidth: this.maxWidth,
|
|
133
145
|
transition: this.transition,
|
|
134
146
|
formatter: this.formatter,
|
|
135
147
|
labelFormatter: this.labelFormatter,
|
|
@@ -454,11 +466,9 @@ export class Tooltip {
|
|
|
454
466
|
if (layouts.length === 0) {
|
|
455
467
|
return;
|
|
456
468
|
}
|
|
457
|
-
this.
|
|
458
|
-
this.resolveSplitTooltipCollisions(layouts, 'side', this.getOppositeSideArrowEdge);
|
|
459
|
-
this.resolveSplitTooltipPositions(layouts);
|
|
469
|
+
this.resolveSplitTooltipPositions(layouts, isHorizontal);
|
|
460
470
|
layouts.forEach((layout) => {
|
|
461
|
-
this.renderTooltipWithConnector(layout.div, layout.arrowEdge, layout.left, layout.top, layout.width, layout.height, layout.targetX, layout.targetY);
|
|
471
|
+
this.renderTooltipWithConnector(layout.div, layout.arrowEdge, layout.left, layout.top, layout.width, layout.height, layout.targetX, layout.targetY, layout.anchor);
|
|
462
472
|
});
|
|
463
473
|
};
|
|
464
474
|
const hideTooltip = () => {
|
|
@@ -706,6 +716,7 @@ export class Tooltip {
|
|
|
706
716
|
theme.tooltip.fontFamily,
|
|
707
717
|
theme.tooltip.fontSize,
|
|
708
718
|
theme.tooltip.fontWeight,
|
|
719
|
+
this.maxWidth,
|
|
709
720
|
this.transition.show,
|
|
710
721
|
this.transition.duration,
|
|
711
722
|
this.transition.easing,
|
|
@@ -724,10 +735,13 @@ export class Tooltip {
|
|
|
724
735
|
.style('font-family', theme.tooltip.fontFamily)
|
|
725
736
|
.style('font-size', `${theme.tooltip.fontSize}px`)
|
|
726
737
|
.style('font-weight', theme.tooltip.fontWeight)
|
|
738
|
+
.style('box-sizing', 'border-box')
|
|
739
|
+
.style('overflow-wrap', 'break-word')
|
|
727
740
|
.style('overflow', 'visible')
|
|
728
741
|
.style('isolation', 'isolate')
|
|
729
742
|
.style('pointer-events', 'none')
|
|
730
743
|
.style('z-index', '1000');
|
|
744
|
+
tooltip.style('max-width', `${this.maxWidth}px`);
|
|
731
745
|
if (this.transition.show) {
|
|
732
746
|
tooltip
|
|
733
747
|
.style('transition', `opacity ${this.transition.duration}ms ${this.transition.easing}, transform ${this.transition.duration}ms ${this.transition.easing}`)
|
|
@@ -758,7 +772,7 @@ export class Tooltip {
|
|
|
758
772
|
height: tooltipRect.height,
|
|
759
773
|
};
|
|
760
774
|
}
|
|
761
|
-
renderTooltipWithConnector(tooltip, arrowEdge, left, top, tooltipWidth, tooltipHeight, targetX, targetY) {
|
|
775
|
+
renderTooltipWithConnector(tooltip, arrowEdge, left, top, tooltipWidth, tooltipHeight, targetX, targetY, anchor) {
|
|
762
776
|
if (!Number.isFinite(left) ||
|
|
763
777
|
!Number.isFinite(top) ||
|
|
764
778
|
!Number.isFinite(targetX) ||
|
|
@@ -766,7 +780,7 @@ export class Tooltip {
|
|
|
766
780
|
this.hideTooltipSelection(tooltip);
|
|
767
781
|
return;
|
|
768
782
|
}
|
|
769
|
-
const connectorLayout = this.resolveTooltipConnectorLayout(arrowEdge, left, top, tooltipWidth, tooltipHeight, targetX, targetY);
|
|
783
|
+
const connectorLayout = this.resolveTooltipConnectorLayout(arrowEdge, left, top, tooltipWidth, tooltipHeight, targetX, targetY, anchor);
|
|
770
784
|
if (!connectorLayout) {
|
|
771
785
|
this.hideTooltipSelection(tooltip);
|
|
772
786
|
return;
|
|
@@ -1100,7 +1114,7 @@ export class Tooltip {
|
|
|
1100
1114
|
top: Math.max(minTop, Math.min(top, maxTop)),
|
|
1101
1115
|
};
|
|
1102
1116
|
}
|
|
1103
|
-
resolveTooltipConnectorLayout(arrowEdge, tooltipLeft, tooltipTop, tooltipWidth, tooltipHeight, targetX, targetY) {
|
|
1117
|
+
resolveTooltipConnectorLayout(arrowEdge, tooltipLeft, tooltipTop, tooltipWidth, tooltipHeight, targetX, targetY, anchor) {
|
|
1104
1118
|
const localTargetX = targetX - tooltipLeft;
|
|
1105
1119
|
const localTargetY = targetY - tooltipTop;
|
|
1106
1120
|
if (!Number.isFinite(localTargetX) || !Number.isFinite(localTargetY)) {
|
|
@@ -1120,7 +1134,9 @@ export class Tooltip {
|
|
|
1120
1134
|
const startY = arrowTip.y - minY;
|
|
1121
1135
|
const endX = localTargetX - minX;
|
|
1122
1136
|
const endY = localTargetY - minY;
|
|
1123
|
-
const
|
|
1137
|
+
const arrowTipX = tooltipLeft + arrowTip.x;
|
|
1138
|
+
const arrowTipY = tooltipTop + arrowTip.y;
|
|
1139
|
+
const connectorPath = this.resolveTooltipConnectorPath(arrowEdge, startX, startY, endX, endY, arrowTipX, arrowTipY, anchor);
|
|
1124
1140
|
if (!this.hasFiniteNumbers(width, height, boxX, boxY, startX, startY, endX, endY)) {
|
|
1125
1141
|
return null;
|
|
1126
1142
|
}
|
|
@@ -1163,21 +1179,31 @@ export class Tooltip {
|
|
|
1163
1179
|
};
|
|
1164
1180
|
}
|
|
1165
1181
|
}
|
|
1166
|
-
resolveTooltipConnectorPath(arrowEdge, startX, startY, endX, endY) {
|
|
1182
|
+
resolveTooltipConnectorPath(arrowEdge, startX, startY, endX, endY, arrowTipX, arrowTipY, anchor) {
|
|
1167
1183
|
if (arrowEdge === 'left' || arrowEdge === 'right') {
|
|
1168
|
-
if (
|
|
1169
|
-
TOOLTIP_CONNECTOR_ALIGNMENT_TOLERANCE_PX) {
|
|
1184
|
+
if (this.isTooltipArrowTipInsideVerticalAnchorSpan(arrowTipY, anchor)) {
|
|
1170
1185
|
return '';
|
|
1171
1186
|
}
|
|
1172
1187
|
const elbowX = startX + (endX - startX) * TOOLTIP_CONNECTOR_ELBOW_RATIO;
|
|
1173
1188
|
return `M ${startX},${startY} L ${elbowX},${startY} L ${endX},${endY}`;
|
|
1174
1189
|
}
|
|
1175
|
-
if (
|
|
1190
|
+
if (this.isTooltipArrowTipInsideHorizontalAnchorSpan(arrowTipX, anchor)) {
|
|
1176
1191
|
return '';
|
|
1177
1192
|
}
|
|
1178
1193
|
const elbowY = startY + (endY - startY) * TOOLTIP_CONNECTOR_ELBOW_RATIO;
|
|
1179
1194
|
return `M ${startX},${startY} L ${startX},${elbowY} L ${endX},${endY}`;
|
|
1180
1195
|
}
|
|
1196
|
+
isTooltipArrowTipInsideHorizontalAnchorSpan(arrowTipX, anchor) {
|
|
1197
|
+
return (arrowTipX >=
|
|
1198
|
+
anchor.left - TOOLTIP_CONNECTOR_ALIGNMENT_TOLERANCE_PX &&
|
|
1199
|
+
arrowTipX <= anchor.right + TOOLTIP_CONNECTOR_ALIGNMENT_TOLERANCE_PX);
|
|
1200
|
+
}
|
|
1201
|
+
isTooltipArrowTipInsideVerticalAnchorSpan(arrowTipY, anchor) {
|
|
1202
|
+
return (arrowTipY >=
|
|
1203
|
+
anchor.top - TOOLTIP_CONNECTOR_ALIGNMENT_TOLERANCE_PX &&
|
|
1204
|
+
arrowTipY <=
|
|
1205
|
+
anchor.bottom + TOOLTIP_CONNECTOR_ALIGNMENT_TOLERANCE_PX);
|
|
1206
|
+
}
|
|
1181
1207
|
resolveTooltipArrowTip(arrowEdge, boxX, boxY, length) {
|
|
1182
1208
|
if (arrowEdge === 'left' || arrowEdge === 'right') {
|
|
1183
1209
|
return {
|
|
@@ -1198,7 +1224,12 @@ export class Tooltip {
|
|
|
1198
1224
|
return;
|
|
1199
1225
|
}
|
|
1200
1226
|
const placedLayouts = [];
|
|
1201
|
-
const orderedLayouts = [...layouts].sort((a, b) =>
|
|
1227
|
+
const orderedLayouts = [...layouts].sort((a, b) => {
|
|
1228
|
+
if (position === 'vertical') {
|
|
1229
|
+
return a.targetX - b.targetX || a.targetY - b.targetY;
|
|
1230
|
+
}
|
|
1231
|
+
return a.targetY - b.targetY || a.targetX - b.targetX;
|
|
1232
|
+
});
|
|
1202
1233
|
orderedLayouts.forEach((layout) => {
|
|
1203
1234
|
this.flipTooltipIfItReducesCollisions(layout, placedLayouts, getOppositeArrowEdge);
|
|
1204
1235
|
placedLayouts.push(layout);
|
|
@@ -1242,7 +1273,12 @@ export class Tooltip {
|
|
|
1242
1273
|
top: flippedPosition.top,
|
|
1243
1274
|
};
|
|
1244
1275
|
const flippedCollisions = this.countSplitTooltipCollisions(flippedLayout, placedLayouts);
|
|
1245
|
-
if (flippedCollisions
|
|
1276
|
+
if (flippedCollisions > currentCollisions) {
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
if (flippedCollisions === currentCollisions &&
|
|
1280
|
+
this.countPlacedLayoutsOnEdge(placedLayouts, flippedArrowEdge) >=
|
|
1281
|
+
this.countPlacedLayoutsOnEdge(placedLayouts, layout.arrowEdge)) {
|
|
1246
1282
|
return;
|
|
1247
1283
|
}
|
|
1248
1284
|
layout.arrowEdge = flippedArrowEdge;
|
|
@@ -1252,15 +1288,53 @@ export class Tooltip {
|
|
|
1252
1288
|
countSplitTooltipCollisions(layout, placedLayouts) {
|
|
1253
1289
|
return placedLayouts.filter((placedLayout) => this.doSplitTooltipLayoutsOverlap(layout, placedLayout)).length;
|
|
1254
1290
|
}
|
|
1291
|
+
countPlacedLayoutsOnEdge(placedLayouts, arrowEdge) {
|
|
1292
|
+
return placedLayouts.filter((layout) => layout.arrowEdge === arrowEdge)
|
|
1293
|
+
.length;
|
|
1294
|
+
}
|
|
1255
1295
|
doSplitTooltipLayoutsOverlap(a, b) {
|
|
1256
1296
|
return (a.left < b.left + b.width + SPLIT_TOOLTIP_GAP_PX &&
|
|
1257
1297
|
a.left + a.width + SPLIT_TOOLTIP_GAP_PX > b.left &&
|
|
1258
1298
|
a.top < b.top + b.height + SPLIT_TOOLTIP_GAP_PX &&
|
|
1259
1299
|
a.top + a.height + SPLIT_TOOLTIP_GAP_PX > b.top);
|
|
1260
1300
|
}
|
|
1261
|
-
resolveSplitTooltipPositions(layouts) {
|
|
1262
|
-
|
|
1263
|
-
|
|
1301
|
+
resolveSplitTooltipPositions(layouts, isHorizontal) {
|
|
1302
|
+
if (isHorizontal) {
|
|
1303
|
+
this.resolveHorizontalChartSplitTooltipPositions(layouts);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
this.resolveVerticalChartSplitTooltipPositions(layouts);
|
|
1307
|
+
}
|
|
1308
|
+
resolveHorizontalChartSplitTooltipPositions(layouts) {
|
|
1309
|
+
if (this.position === 'vertical') {
|
|
1310
|
+
this.resolveSplitTooltipCollisions(layouts, 'vertical', this.getOppositeVerticalArrowEdge);
|
|
1311
|
+
this.resolveHorizontalChartAboveBelowSplitTooltipPositions(layouts);
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
this.resolveHorizontalSideSplitTooltipPositions(layouts);
|
|
1315
|
+
}
|
|
1316
|
+
resolveVerticalChartSplitTooltipPositions(layouts) {
|
|
1317
|
+
if (this.position === 'vertical') {
|
|
1318
|
+
this.resolveSplitTooltipCollisions(layouts, 'vertical', this.getOppositeVerticalArrowEdge);
|
|
1319
|
+
this.resolveVerticalChartAboveBelowSplitTooltipPositions(layouts);
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
this.resolveSplitTooltipCollisions(layouts, 'side', this.getOppositeSideArrowEdge);
|
|
1323
|
+
this.resolveSideSplitTooltipPositions(layouts);
|
|
1324
|
+
}
|
|
1325
|
+
getSplitTooltipViewportBounds() {
|
|
1326
|
+
return {
|
|
1327
|
+
minLeft: window.scrollX + TOOLTIP_VIEWPORT_PADDING_PX,
|
|
1328
|
+
maxRight: window.scrollX +
|
|
1329
|
+
window.innerWidth -
|
|
1330
|
+
TOOLTIP_VIEWPORT_PADDING_PX,
|
|
1331
|
+
minTop: window.scrollY + TOOLTIP_VIEWPORT_PADDING_PX,
|
|
1332
|
+
maxBottom: window.scrollY +
|
|
1333
|
+
window.innerHeight -
|
|
1334
|
+
TOOLTIP_VIEWPORT_PADDING_PX,
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
groupSplitTooltipLayoutsByEdge(layouts) {
|
|
1264
1338
|
const tooltipsByEdge = {
|
|
1265
1339
|
left: [],
|
|
1266
1340
|
right: [],
|
|
@@ -1270,6 +1344,11 @@ export class Tooltip {
|
|
|
1270
1344
|
layouts.forEach((layout) => {
|
|
1271
1345
|
tooltipsByEdge[layout.arrowEdge].push(layout);
|
|
1272
1346
|
});
|
|
1347
|
+
return tooltipsByEdge;
|
|
1348
|
+
}
|
|
1349
|
+
resolveSideSplitTooltipPositions(layouts) {
|
|
1350
|
+
const { minTop, maxBottom } = this.getSplitTooltipViewportBounds();
|
|
1351
|
+
const tooltipsByEdge = this.groupSplitTooltipLayoutsByEdge(layouts);
|
|
1273
1352
|
Object.values(tooltipsByEdge).forEach((edgeLayouts) => {
|
|
1274
1353
|
if (edgeLayouts.length === 0) {
|
|
1275
1354
|
return;
|
|
@@ -1309,6 +1388,197 @@ export class Tooltip {
|
|
|
1309
1388
|
});
|
|
1310
1389
|
});
|
|
1311
1390
|
}
|
|
1391
|
+
resolveHorizontalSideSplitTooltipPositions(layouts) {
|
|
1392
|
+
this.resolveHorizontalSideSplitTooltipCollisions(layouts);
|
|
1393
|
+
const minTop = window.scrollY + TOOLTIP_VIEWPORT_PADDING_PX;
|
|
1394
|
+
const maxBottom = window.scrollY + window.innerHeight - TOOLTIP_VIEWPORT_PADDING_PX;
|
|
1395
|
+
const lanes = [];
|
|
1396
|
+
const orderedLayouts = [...layouts].sort((a, b) => a.targetX - b.targetX || a.left - b.left);
|
|
1397
|
+
orderedLayouts.forEach((layout) => {
|
|
1398
|
+
const maxTop = maxBottom - layout.height;
|
|
1399
|
+
const preferredTop = Math.max(minTop, Math.min(layout.top, maxTop));
|
|
1400
|
+
const reusableLane = this.findReusableHorizontalTooltipLane(lanes, layout, preferredTop);
|
|
1401
|
+
if (reusableLane) {
|
|
1402
|
+
this.assignLayoutToHorizontalTooltipLane(layout, reusableLane);
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
const nextLane = {
|
|
1406
|
+
top: this.resolveNewHorizontalTooltipLaneTop(preferredTop, layout.height, minTop, maxTop, lanes),
|
|
1407
|
+
layouts: [],
|
|
1408
|
+
};
|
|
1409
|
+
lanes.push(nextLane);
|
|
1410
|
+
this.assignLayoutToHorizontalTooltipLane(layout, nextLane);
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
resolveHorizontalSideSplitTooltipCollisions(layouts) {
|
|
1414
|
+
const placedLayouts = [];
|
|
1415
|
+
const orderedLayouts = [...layouts].sort((a, b) => a.targetX - b.targetX || a.left - b.left);
|
|
1416
|
+
orderedLayouts.forEach((layout) => {
|
|
1417
|
+
this.flipHorizontalSideTooltipIfItReducesCollisions(layout, placedLayouts);
|
|
1418
|
+
placedLayouts.push(layout);
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
flipHorizontalSideTooltipIfItReducesCollisions(layout, placedLayouts) {
|
|
1422
|
+
const currentCollisions = this.countSplitTooltipCollisions(layout, placedLayouts);
|
|
1423
|
+
if (currentCollisions === 0) {
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
const flippedArrowEdge = this.getOppositeSideArrowEdge(layout.arrowEdge);
|
|
1427
|
+
if (!flippedArrowEdge) {
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
const flippedPosition = this.getAnchoredTooltipPosition(layout.anchor, { x: layout.targetX, y: layout.targetY }, layout.width, layout.height, flippedArrowEdge);
|
|
1431
|
+
if (!flippedPosition) {
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
const flippedLayout = {
|
|
1435
|
+
...layout,
|
|
1436
|
+
arrowEdge: flippedArrowEdge,
|
|
1437
|
+
left: flippedPosition.left,
|
|
1438
|
+
top: flippedPosition.top,
|
|
1439
|
+
};
|
|
1440
|
+
const flippedCollisions = this.countSplitTooltipCollisions(flippedLayout, placedLayouts);
|
|
1441
|
+
if (flippedCollisions > currentCollisions) {
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
if (flippedCollisions === currentCollisions &&
|
|
1445
|
+
this.countPlacedLayoutsOnEdge(placedLayouts, flippedArrowEdge) >=
|
|
1446
|
+
this.countPlacedLayoutsOnEdge(placedLayouts, layout.arrowEdge)) {
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
layout.arrowEdge = flippedArrowEdge;
|
|
1450
|
+
layout.left = flippedPosition.left;
|
|
1451
|
+
layout.top = flippedPosition.top;
|
|
1452
|
+
}
|
|
1453
|
+
findReusableHorizontalTooltipLane(lanes, layout, preferredTop) {
|
|
1454
|
+
const reusableLanes = lanes
|
|
1455
|
+
.filter((lane) => lane.layouts.every((placedLayout) => !this.doSplitTooltipLayoutsOverlapHorizontally(layout, placedLayout)))
|
|
1456
|
+
.sort((a, b) => Math.abs(a.top - preferredTop) -
|
|
1457
|
+
Math.abs(b.top - preferredTop));
|
|
1458
|
+
return reusableLanes[0] ?? null;
|
|
1459
|
+
}
|
|
1460
|
+
resolveNewHorizontalTooltipLaneTop(preferredTop, tooltipHeight, minTop, maxTop, lanes) {
|
|
1461
|
+
const usedTops = new Set(lanes.map((lane) => Math.round(lane.top)));
|
|
1462
|
+
const candidates = this.getHorizontalTooltipLaneTopCandidates(preferredTop, tooltipHeight, minTop, maxTop);
|
|
1463
|
+
return (candidates.find((candidate) => !usedTops.has(Math.round(candidate)) &&
|
|
1464
|
+
lanes.every((lane) => !this.doHorizontalTooltipLanesOverlap(candidate, tooltipHeight, lane))) ??
|
|
1465
|
+
candidates[0] ??
|
|
1466
|
+
preferredTop);
|
|
1467
|
+
}
|
|
1468
|
+
getHorizontalTooltipLaneTopCandidates(preferredTop, tooltipHeight, minTop, maxTop) {
|
|
1469
|
+
const step = tooltipHeight + SPLIT_TOOLTIP_GAP_PX;
|
|
1470
|
+
const candidates = [preferredTop];
|
|
1471
|
+
for (let index = 1; index <= 8; index++) {
|
|
1472
|
+
candidates.push(preferredTop - step * index);
|
|
1473
|
+
candidates.push(preferredTop + step * index);
|
|
1474
|
+
}
|
|
1475
|
+
return Array.from(new Set(candidates.map((candidate) => Math.round(Math.max(minTop, Math.min(candidate, maxTop)))))).sort((a, b) => Math.abs(a - preferredTop) - Math.abs(b - preferredTop));
|
|
1476
|
+
}
|
|
1477
|
+
assignLayoutToHorizontalTooltipLane(layout, lane) {
|
|
1478
|
+
layout.top = lane.top;
|
|
1479
|
+
lane.layouts.push(layout);
|
|
1480
|
+
}
|
|
1481
|
+
doHorizontalTooltipLanesOverlap(top, height, lane) {
|
|
1482
|
+
const laneHeight = lane.layouts.reduce((maxHeight, layout) => Math.max(maxHeight, layout.height), 0);
|
|
1483
|
+
return (top < lane.top + laneHeight + SPLIT_TOOLTIP_GAP_PX &&
|
|
1484
|
+
top + height + SPLIT_TOOLTIP_GAP_PX > lane.top);
|
|
1485
|
+
}
|
|
1486
|
+
doSplitTooltipLayoutsOverlapHorizontally(a, b) {
|
|
1487
|
+
return (a.left < b.left + b.width + SPLIT_TOOLTIP_GAP_PX &&
|
|
1488
|
+
a.left + a.width + SPLIT_TOOLTIP_GAP_PX > b.left);
|
|
1489
|
+
}
|
|
1490
|
+
resolveHorizontalChartAboveBelowSplitTooltipPositions(layouts) {
|
|
1491
|
+
const bounds = this.getSplitTooltipViewportBounds();
|
|
1492
|
+
const tooltipsByEdge = this.groupSplitTooltipLayoutsByEdge(layouts);
|
|
1493
|
+
this.resolvePackedAboveBelowTooltipRow(tooltipsByEdge.top, bounds);
|
|
1494
|
+
this.resolvePackedAboveBelowTooltipRow(tooltipsByEdge.bottom, bounds);
|
|
1495
|
+
this.resolveSideSplitTooltipPositions([
|
|
1496
|
+
...tooltipsByEdge.left,
|
|
1497
|
+
...tooltipsByEdge.right,
|
|
1498
|
+
]);
|
|
1499
|
+
}
|
|
1500
|
+
resolvePackedAboveBelowTooltipRow(layouts, bounds) {
|
|
1501
|
+
if (layouts.length === 0) {
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
const orderedLayouts = [...layouts].sort((a, b) => a.left - b.left || a.targetX - b.targetX);
|
|
1505
|
+
orderedLayouts.forEach((layout) => {
|
|
1506
|
+
const maxTop = Math.max(bounds.minTop, bounds.maxBottom - layout.height);
|
|
1507
|
+
layout.top = Math.max(bounds.minTop, Math.min(layout.top, maxTop));
|
|
1508
|
+
});
|
|
1509
|
+
const firstLayout = orderedLayouts[0];
|
|
1510
|
+
const firstMaxLeft = Math.max(bounds.minLeft, bounds.maxRight - firstLayout.width);
|
|
1511
|
+
firstLayout.left = Math.max(bounds.minLeft, Math.min(firstLayout.left, firstMaxLeft));
|
|
1512
|
+
for (let i = 1; i < orderedLayouts.length; i++) {
|
|
1513
|
+
const previousLayout = orderedLayouts[i - 1];
|
|
1514
|
+
const currentLayout = orderedLayouts[i];
|
|
1515
|
+
const minAllowedLeft = previousLayout.left +
|
|
1516
|
+
previousLayout.width +
|
|
1517
|
+
SPLIT_TOOLTIP_GAP_PX;
|
|
1518
|
+
currentLayout.left = Math.max(currentLayout.left, minAllowedLeft);
|
|
1519
|
+
}
|
|
1520
|
+
const lastLayout = orderedLayouts[orderedLayouts.length - 1];
|
|
1521
|
+
const overflow = lastLayout.left + lastLayout.width - bounds.maxRight;
|
|
1522
|
+
if (overflow > 0) {
|
|
1523
|
+
lastLayout.left -= overflow;
|
|
1524
|
+
for (let i = orderedLayouts.length - 2; i >= 0; i--) {
|
|
1525
|
+
const currentLayout = orderedLayouts[i];
|
|
1526
|
+
const nextLayout = orderedLayouts[i + 1];
|
|
1527
|
+
const maxAllowedLeft = nextLayout.left -
|
|
1528
|
+
currentLayout.width -
|
|
1529
|
+
SPLIT_TOOLTIP_GAP_PX;
|
|
1530
|
+
currentLayout.left = Math.min(currentLayout.left, maxAllowedLeft);
|
|
1531
|
+
}
|
|
1532
|
+
const underflow = bounds.minLeft - orderedLayouts[0].left;
|
|
1533
|
+
if (underflow > 0) {
|
|
1534
|
+
orderedLayouts.forEach((layout) => {
|
|
1535
|
+
layout.left += underflow;
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
orderedLayouts.forEach((layout) => {
|
|
1540
|
+
const maxLeft = Math.max(bounds.minLeft, bounds.maxRight - layout.width);
|
|
1541
|
+
layout.left = Math.max(bounds.minLeft, Math.min(layout.left, maxLeft));
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
resolveVerticalChartAboveBelowSplitTooltipPositions(layouts) {
|
|
1545
|
+
const bounds = this.getSplitTooltipViewportBounds();
|
|
1546
|
+
const tooltipsByEdge = this.groupSplitTooltipLayoutsByEdge(layouts);
|
|
1547
|
+
this.resolveCollisionAwareAboveBelowTooltipPositions(tooltipsByEdge.top, bounds);
|
|
1548
|
+
this.resolveCollisionAwareAboveBelowTooltipPositions(tooltipsByEdge.bottom, bounds);
|
|
1549
|
+
this.resolveSideSplitTooltipPositions([
|
|
1550
|
+
...tooltipsByEdge.left,
|
|
1551
|
+
...tooltipsByEdge.right,
|
|
1552
|
+
]);
|
|
1553
|
+
}
|
|
1554
|
+
resolveCollisionAwareAboveBelowTooltipPositions(layouts, bounds) {
|
|
1555
|
+
const placedLayouts = [];
|
|
1556
|
+
const orderedLayouts = [...layouts].sort((a, b) => a.left - b.left || a.top - b.top);
|
|
1557
|
+
orderedLayouts.forEach((layout) => {
|
|
1558
|
+
const maxLeft = Math.max(bounds.minLeft, bounds.maxRight - layout.width);
|
|
1559
|
+
const maxTop = Math.max(bounds.minTop, bounds.maxBottom - layout.height);
|
|
1560
|
+
layout.top = Math.max(bounds.minTop, Math.min(layout.top, maxTop));
|
|
1561
|
+
layout.left = this.resolveNonOverlappingAboveBelowTooltipLeft(layout, placedLayouts, bounds.minLeft, maxLeft);
|
|
1562
|
+
placedLayouts.push(layout);
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
resolveNonOverlappingAboveBelowTooltipLeft(layout, placedLayouts, minLeft, maxLeft) {
|
|
1566
|
+
const preferredLeft = Math.max(minLeft, Math.min(layout.left, maxLeft));
|
|
1567
|
+
if (!this.doesSplitTooltipOverlapPlacedLayouts({ ...layout, left: preferredLeft }, placedLayouts)) {
|
|
1568
|
+
return preferredLeft;
|
|
1569
|
+
}
|
|
1570
|
+
const candidates = placedLayouts.flatMap((placedLayout) => [
|
|
1571
|
+
placedLayout.left + placedLayout.width + SPLIT_TOOLTIP_GAP_PX,
|
|
1572
|
+
placedLayout.left - layout.width - SPLIT_TOOLTIP_GAP_PX,
|
|
1573
|
+
]);
|
|
1574
|
+
return (Array.from(new Set(candidates.map((candidate) => Math.round(Math.max(minLeft, Math.min(candidate, maxLeft))))))
|
|
1575
|
+
.sort((a, b) => Math.abs(a - preferredLeft) -
|
|
1576
|
+
Math.abs(b - preferredLeft))
|
|
1577
|
+
.find((candidate) => !this.doesSplitTooltipOverlapPlacedLayouts({ ...layout, left: candidate }, placedLayouts)) ?? preferredLeft);
|
|
1578
|
+
}
|
|
1579
|
+
doesSplitTooltipOverlapPlacedLayouts(layout, placedLayouts) {
|
|
1580
|
+
return placedLayouts.some((placedLayout) => this.doSplitTooltipLayoutsOverlap(layout, placedLayout));
|
|
1581
|
+
}
|
|
1312
1582
|
}
|
|
1313
1583
|
Object.defineProperty(Tooltip, "nextTooltipId", {
|
|
1314
1584
|
enumerable: true,
|
package/dist/types.d.ts
CHANGED
|
@@ -253,6 +253,7 @@ export type BarStackConfig = {
|
|
|
253
253
|
export type AreaStackMode = 'none' | 'normal' | 'percent';
|
|
254
254
|
export type AreaStackConfig = {
|
|
255
255
|
mode?: AreaStackMode;
|
|
256
|
+
reverseSeries?: boolean;
|
|
256
257
|
};
|
|
257
258
|
export declare function getSeriesColor(series: {
|
|
258
259
|
stroke?: string;
|
|
@@ -308,6 +309,7 @@ export type TooltipConfigBase = {
|
|
|
308
309
|
mode?: TooltipMode;
|
|
309
310
|
position?: TooltipPosition;
|
|
310
311
|
barAnchorPosition?: TooltipBarAnchorPosition;
|
|
312
|
+
maxWidth?: number;
|
|
311
313
|
transition?: TooltipTransitionConfig;
|
|
312
314
|
formatter?: SeriesValueFormatter;
|
|
313
315
|
labelFormatter?: (label: string, data: DataItem) => string;
|
package/dist/xy-chart.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare class XYChart extends BaseChart {
|
|
|
15
15
|
private barStackGap;
|
|
16
16
|
private barStackReverseSeries;
|
|
17
17
|
private areaStackMode;
|
|
18
|
+
private areaStackReverseSeries;
|
|
18
19
|
private readonly orientation;
|
|
19
20
|
private readonly motionDriver;
|
|
20
21
|
private scaleConfigOverride;
|
|
@@ -39,6 +40,8 @@ export declare class XYChart extends BaseChart {
|
|
|
39
40
|
private resolveValueAxisDomain;
|
|
40
41
|
private getVisibleSeries;
|
|
41
42
|
private getDisplaySeries;
|
|
43
|
+
private getBarDisplaySeries;
|
|
44
|
+
private getAreaDisplaySeries;
|
|
42
45
|
private resolveSeriesDefaults;
|
|
43
46
|
private shouldReplaceSeriesColor;
|
|
44
47
|
private cloneSeriesWithOverride;
|
package/dist/xy-chart.js
CHANGED
|
@@ -14,8 +14,11 @@ function resolveBarStackSettings(config) {
|
|
|
14
14
|
reverseSeries: config.barStack?.reverseSeries ?? false,
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
|
-
function
|
|
18
|
-
return
|
|
17
|
+
function resolveAreaStackSettings(config) {
|
|
18
|
+
return {
|
|
19
|
+
mode: config.areaStack?.mode ?? 'none',
|
|
20
|
+
reverseSeries: config.areaStack?.reverseSeries ?? false,
|
|
21
|
+
};
|
|
19
22
|
}
|
|
20
23
|
function isXYSeries(component) {
|
|
21
24
|
return (component.type === 'line' ||
|
|
@@ -56,6 +59,12 @@ export class XYChart extends BaseChart {
|
|
|
56
59
|
writable: true,
|
|
57
60
|
value: void 0
|
|
58
61
|
});
|
|
62
|
+
Object.defineProperty(this, "areaStackReverseSeries", {
|
|
63
|
+
enumerable: true,
|
|
64
|
+
configurable: true,
|
|
65
|
+
writable: true,
|
|
66
|
+
value: void 0
|
|
67
|
+
});
|
|
59
68
|
Object.defineProperty(this, "orientation", {
|
|
60
69
|
enumerable: true,
|
|
61
70
|
configurable: true,
|
|
@@ -75,11 +84,13 @@ export class XYChart extends BaseChart {
|
|
|
75
84
|
value: null
|
|
76
85
|
});
|
|
77
86
|
const barStack = resolveBarStackSettings(config);
|
|
87
|
+
const areaStack = resolveAreaStackSettings(config);
|
|
78
88
|
this.orientation = config.orientation ?? 'vertical';
|
|
79
89
|
this.barStackMode = barStack.mode;
|
|
80
90
|
this.barStackGap = barStack.gap;
|
|
81
91
|
this.barStackReverseSeries = barStack.reverseSeries;
|
|
82
|
-
this.areaStackMode =
|
|
92
|
+
this.areaStackMode = areaStack.mode;
|
|
93
|
+
this.areaStackReverseSeries = areaStack.reverseSeries;
|
|
83
94
|
this.motionDriver = createXYMotionDriver(config.animate);
|
|
84
95
|
}
|
|
85
96
|
addChild(component) {
|
|
@@ -120,6 +131,7 @@ export class XYChart extends BaseChart {
|
|
|
120
131
|
},
|
|
121
132
|
areaStack: {
|
|
122
133
|
mode: this.areaStackMode,
|
|
134
|
+
reverseSeries: this.areaStackReverseSeries,
|
|
123
135
|
},
|
|
124
136
|
animate: false,
|
|
125
137
|
});
|
|
@@ -289,18 +301,22 @@ export class XYChart extends BaseChart {
|
|
|
289
301
|
});
|
|
290
302
|
}
|
|
291
303
|
getDisplaySeries() {
|
|
304
|
+
const barOrderedSeries = this.getBarDisplaySeries(this.series);
|
|
305
|
+
return this.getAreaDisplaySeries(barOrderedSeries);
|
|
306
|
+
}
|
|
307
|
+
getBarDisplaySeries(series) {
|
|
292
308
|
if (!this.barStackReverseSeries) {
|
|
293
|
-
return
|
|
309
|
+
return series;
|
|
294
310
|
}
|
|
295
|
-
const barSeries =
|
|
311
|
+
const barSeries = series.filter((entry) => {
|
|
296
312
|
return entry.type === 'bar';
|
|
297
313
|
});
|
|
298
314
|
if (barSeries.length < 2) {
|
|
299
|
-
return
|
|
315
|
+
return series;
|
|
300
316
|
}
|
|
301
317
|
const reversedBars = [...barSeries].reverse();
|
|
302
318
|
let reversedBarIndex = 0;
|
|
303
|
-
return
|
|
319
|
+
return series.map((entry) => {
|
|
304
320
|
if (entry.type !== 'bar') {
|
|
305
321
|
return entry;
|
|
306
322
|
}
|
|
@@ -309,6 +325,31 @@ export class XYChart extends BaseChart {
|
|
|
309
325
|
return nextBar;
|
|
310
326
|
});
|
|
311
327
|
}
|
|
328
|
+
getAreaDisplaySeries(series) {
|
|
329
|
+
if (!this.areaStackReverseSeries || this.areaStackMode === 'none') {
|
|
330
|
+
return series;
|
|
331
|
+
}
|
|
332
|
+
const areaSeries = series.filter((entry) => {
|
|
333
|
+
return entry.type === 'area';
|
|
334
|
+
});
|
|
335
|
+
const stackGroups = this.getStackedAreaGroups(areaSeries);
|
|
336
|
+
if (stackGroups.size === 0) {
|
|
337
|
+
return series;
|
|
338
|
+
}
|
|
339
|
+
const reversedSeriesByOriginal = new Map();
|
|
340
|
+
stackGroups.forEach((stackSeries) => {
|
|
341
|
+
const reversedStackSeries = [...stackSeries].reverse();
|
|
342
|
+
stackSeries.forEach((entry, index) => {
|
|
343
|
+
reversedSeriesByOriginal.set(entry, reversedStackSeries[index]);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
return series.map((entry) => {
|
|
347
|
+
if (entry.type !== 'area') {
|
|
348
|
+
return entry;
|
|
349
|
+
}
|
|
350
|
+
return reversedSeriesByOriginal.get(entry) ?? entry;
|
|
351
|
+
});
|
|
352
|
+
}
|
|
312
353
|
resolveSeriesDefaults(series) {
|
|
313
354
|
const colorIndex = this.series.length % this.theme.colorPalette.length;
|
|
314
355
|
const paletteColor = this.theme.colorPalette[colorIndex];
|
package/docs/components.md
CHANGED
|
@@ -122,6 +122,7 @@ new Tooltip({
|
|
|
122
122
|
mode?: 'shared' | 'split',
|
|
123
123
|
position?: 'side' | 'vertical',
|
|
124
124
|
barAnchorPosition?: 'top' | 'middle',
|
|
125
|
+
maxWidth?: number, // default: 280
|
|
125
126
|
transition?: {
|
|
126
127
|
show?: boolean,
|
|
127
128
|
duration?: number,
|
|
@@ -155,6 +156,9 @@ possible.
|
|
|
155
156
|
For bars, `barAnchorPosition` controls whether split tooltips point to the `top`
|
|
156
157
|
or `middle` of each bar.
|
|
157
158
|
|
|
159
|
+
Tooltips default to a `280px` max width so longer content wraps. Set `maxWidth`
|
|
160
|
+
to choose a different cap in pixels.
|
|
161
|
+
|
|
158
162
|
Set `transition.show: true` to fade tooltips in and out. Tooltip position and
|
|
159
163
|
connector geometry update immediately; only opacity and the small entrance
|
|
160
164
|
offset transition.
|
package/docs/xy-chart.md
CHANGED
|
@@ -20,7 +20,7 @@ new XYChart(config: XYChartConfig)
|
|
|
20
20
|
| `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Chart orientation. Horizontal mode currently supports bar-only charts |
|
|
21
21
|
| `responsive` | `ResponsiveConfig` | - | Container-query responsive overrides (theme + components) |
|
|
22
22
|
| `barStack` | `BarStackConfig` | `{ mode: 'normal', gap: 0.1, reverseSeries: false }` | Bar stacking configuration |
|
|
23
|
-
| `areaStack` | `AreaStackConfig` | `{ mode: 'none' }`
|
|
23
|
+
| `areaStack` | `AreaStackConfig` | `{ mode: 'none', reverseSeries: false }` | Area stacking configuration |
|
|
24
24
|
| `animate` | `boolean \| XYAnimationConfig` | `false` | Opt-in XY series animation for initial render and `update()` |
|
|
25
25
|
|
|
26
26
|
### Theme Options
|
|
@@ -515,6 +515,8 @@ series at the hovered category. Use
|
|
|
515
515
|
to override the grouping and split-tooltip placement.
|
|
516
516
|
For bars, set `barAnchorPosition: 'top' | 'middle'` to choose whether split
|
|
517
517
|
tooltips point to the top or middle of each bar.
|
|
518
|
+
Tooltips default to a `280px` max width. Set `maxWidth` to choose a different
|
|
519
|
+
cap in pixels.
|
|
518
520
|
Use `transition: { show: true, duration: 120, easing: 'ease-out' }` to opt in
|
|
519
521
|
to softer tooltip opacity transitions without delaying position updates.
|
|
520
522
|
|
|
@@ -576,12 +578,16 @@ Area charts support stacking when series share the same `stackId`:
|
|
|
576
578
|
```javascript
|
|
577
579
|
const chart = new XYChart({
|
|
578
580
|
data,
|
|
579
|
-
areaStack: { mode: 'percent' },
|
|
581
|
+
areaStack: { mode: 'percent', reverseSeries: true },
|
|
580
582
|
});
|
|
581
583
|
|
|
582
584
|
chart.addChild(new Area({ dataKey: 'desktop', stackId: 'traffic' })).addChild(new Area({ dataKey: 'mobile', stackId: 'traffic' }));
|
|
583
585
|
```
|
|
584
586
|
|
|
587
|
+
Use `areaStack.reverseSeries: true` to reverse stacked area series display order
|
|
588
|
+
for rendering, legend entries, and split tooltip ordering without changing data
|
|
589
|
+
exports. The reversal applies within each shared `stackId` group.
|
|
590
|
+
|
|
585
591
|
---
|
|
586
592
|
|
|
587
593
|
## Mixed Charts
|
package/package.json
CHANGED