@internetstiftelsen/charts 0.18.1 → 0.19.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 +3 -1
- package/dist/legend.d.ts +4 -1
- package/dist/legend.js +38 -3
- package/dist/tooltip/geometry.js +84 -13
- package/dist/tooltip/types.d.ts +2 -2
- package/dist/tooltip/types.js +2 -2
- package/dist/tooltip/xy-interaction.js +24 -0
- package/dist/types.d.ts +4 -1
- package/docs/components.md +6 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -255,7 +255,9 @@ axis, and responsive behavior. `ChartGroup.height` behaves like chart height:
|
|
|
255
255
|
set it for a fixed total group height, or omit it to size from the render
|
|
256
256
|
container. Set `syncY: true` when you want vertical `XYChart` children to share
|
|
257
257
|
the same Y domain so grid lines stay aligned while only one child renders a
|
|
258
|
-
visible Y axis.
|
|
258
|
+
visible Y axis. Legend rows wrap automatically, with `align` positioning the
|
|
259
|
+
legend block and `textAlign` aligning rows inside it:
|
|
260
|
+
`new Legend({ align: 'center', textAlign: 'left' })`.
|
|
259
261
|
|
|
260
262
|
`ChartGroup` also supports declarative responsive layout overrides. Group
|
|
261
263
|
breakpoints can change `cols` and `gap`, while `addChart(..., options)` can
|
package/dist/legend.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type { LegendConfig, ChartTheme, LegendSeries, ExportHooks, LegendConfigBase, LegendMode } from './types.js';
|
|
2
|
+
import type { LegendConfig, ChartTheme, HorizontalAlignment, LegendSeries, ExportHooks, LegendConfigBase, LegendMode } from './types.js';
|
|
3
3
|
import type { LayoutAwareComponent, ComponentSpace } from './chart-interface.js';
|
|
4
4
|
import { LegendStateController } from './legend-state.js';
|
|
5
5
|
export declare class Legend implements LayoutAwareComponent<LegendConfigBase> {
|
|
@@ -7,6 +7,8 @@ export declare class Legend implements LayoutAwareComponent<LegendConfigBase> {
|
|
|
7
7
|
mode: LegendMode;
|
|
8
8
|
readonly position: LegendConfig['position'];
|
|
9
9
|
readonly disconnectedTarget?: string | HTMLElement;
|
|
10
|
+
readonly align: HorizontalAlignment;
|
|
11
|
+
readonly textAlign: HorizontalAlignment;
|
|
10
12
|
readonly exportHooks?: ExportHooks<LegendConfigBase>;
|
|
11
13
|
private readonly marginTop;
|
|
12
14
|
private readonly marginBottom;
|
|
@@ -53,5 +55,6 @@ export declare class Legend implements LayoutAwareComponent<LegendConfigBase> {
|
|
|
53
55
|
private positionRows;
|
|
54
56
|
private getLayoutSignature;
|
|
55
57
|
private getFallbackRowHeight;
|
|
58
|
+
private getAlignmentOffset;
|
|
56
59
|
private bindStateController;
|
|
57
60
|
}
|
package/dist/legend.js
CHANGED
|
@@ -28,6 +28,18 @@ export class Legend {
|
|
|
28
28
|
writable: true,
|
|
29
29
|
value: void 0
|
|
30
30
|
});
|
|
31
|
+
Object.defineProperty(this, "align", {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
configurable: true,
|
|
34
|
+
writable: true,
|
|
35
|
+
value: void 0
|
|
36
|
+
});
|
|
37
|
+
Object.defineProperty(this, "textAlign", {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
configurable: true,
|
|
40
|
+
writable: true,
|
|
41
|
+
value: void 0
|
|
42
|
+
});
|
|
31
43
|
Object.defineProperty(this, "exportHooks", {
|
|
32
44
|
enumerable: true,
|
|
33
45
|
configurable: true,
|
|
@@ -106,10 +118,12 @@ export class Legend {
|
|
|
106
118
|
writable: true,
|
|
107
119
|
value: null
|
|
108
120
|
});
|
|
109
|
-
const { mode = 'inline', position = 'bottom', disconnectedTarget, marginTop = 20, marginBottom = 10, paddingX, itemSpacingX, itemSpacingY, exportHooks, } = config ?? {};
|
|
121
|
+
const { mode = 'inline', position = 'bottom', disconnectedTarget, align = 'center', textAlign = 'center', marginTop = 20, marginBottom = 10, paddingX, itemSpacingX, itemSpacingY, exportHooks, } = config ?? {};
|
|
110
122
|
this.mode = mode;
|
|
111
123
|
this.position = position;
|
|
112
124
|
this.disconnectedTarget = disconnectedTarget;
|
|
125
|
+
this.align = align;
|
|
126
|
+
this.textAlign = textAlign;
|
|
113
127
|
this.marginTop = marginTop;
|
|
114
128
|
this.marginBottom = marginBottom;
|
|
115
129
|
this.paddingX = paddingX;
|
|
@@ -124,6 +138,8 @@ export class Legend {
|
|
|
124
138
|
mode: this.mode,
|
|
125
139
|
position: this.position,
|
|
126
140
|
disconnectedTarget: this.disconnectedTarget,
|
|
141
|
+
align: this.align,
|
|
142
|
+
textAlign: this.textAlign,
|
|
127
143
|
marginTop: this.marginTop,
|
|
128
144
|
marginBottom: this.marginBottom,
|
|
129
145
|
paddingX: this.paddingX,
|
|
@@ -335,6 +351,8 @@ export class Legend {
|
|
|
335
351
|
paddingX: this.paddingX ?? theme.legend.paddingX,
|
|
336
352
|
itemSpacingX: this.itemSpacingX ?? theme.legend.itemSpacingX,
|
|
337
353
|
itemSpacingY: this.itemSpacingY ?? theme.legend.itemSpacingY,
|
|
354
|
+
align: this.align,
|
|
355
|
+
textAlign: this.textAlign,
|
|
338
356
|
};
|
|
339
357
|
}
|
|
340
358
|
buildLegendItems(series) {
|
|
@@ -402,10 +420,15 @@ export class Legend {
|
|
|
402
420
|
positionRows(rows, width, settings) {
|
|
403
421
|
const positionedItems = [];
|
|
404
422
|
const availableWidth = Math.max(0, width - settings.paddingX * 2);
|
|
423
|
+
const legendBlockWidth = rows.reduce((maxWidth, row) => {
|
|
424
|
+
return Math.max(maxWidth, row.width);
|
|
425
|
+
}, 0);
|
|
426
|
+
const legendBlockX = settings.paddingX +
|
|
427
|
+
this.getAlignmentOffset(availableWidth, legendBlockWidth, settings.align);
|
|
405
428
|
let accumulatedRowHeight = 0;
|
|
406
429
|
rows.forEach((row, rowIndex) => {
|
|
407
|
-
const rowX =
|
|
408
|
-
|
|
430
|
+
const rowX = legendBlockX +
|
|
431
|
+
this.getAlignmentOffset(legendBlockWidth, row.width, settings.textAlign);
|
|
409
432
|
const rowY = accumulatedRowHeight + rowIndex * settings.itemSpacingY;
|
|
410
433
|
let currentX = rowX;
|
|
411
434
|
row.items.forEach((item, itemIndex) => {
|
|
@@ -433,6 +456,8 @@ export class Legend {
|
|
|
433
456
|
theme.legend.paddingX,
|
|
434
457
|
theme.legend.itemSpacingX,
|
|
435
458
|
theme.legend.itemSpacingY,
|
|
459
|
+
this.align,
|
|
460
|
+
this.textAlign,
|
|
436
461
|
this.paddingX ?? '',
|
|
437
462
|
this.itemSpacingX ?? '',
|
|
438
463
|
this.itemSpacingY ?? '',
|
|
@@ -442,6 +467,16 @@ export class Legend {
|
|
|
442
467
|
getFallbackRowHeight(theme) {
|
|
443
468
|
return Math.max(theme.legend.boxSize, theme.legend.fontSize);
|
|
444
469
|
}
|
|
470
|
+
getAlignmentOffset(availableWidth, rowWidth, align) {
|
|
471
|
+
const remainingWidth = Math.max(0, availableWidth - rowWidth);
|
|
472
|
+
if (align === 'left') {
|
|
473
|
+
return 0;
|
|
474
|
+
}
|
|
475
|
+
if (align === 'right') {
|
|
476
|
+
return remainingWidth;
|
|
477
|
+
}
|
|
478
|
+
return remainingWidth / 2;
|
|
479
|
+
}
|
|
445
480
|
bindStateController(controller) {
|
|
446
481
|
this.stateControllerCleanup?.();
|
|
447
482
|
this.stateController = controller;
|
package/dist/tooltip/geometry.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SPLIT_TOOLTIP_GAP_PX, TOOLTIP_BOX_ARROW_LENGTH_PX, TOOLTIP_CONNECTOR_ALIGNMENT_TOLERANCE_PX, TOOLTIP_CONNECTOR_ELBOW_RATIO, TOOLTIP_CONNECTOR_INSET_PX, TOOLTIP_CONNECTOR_MIN_PATH_LENGTH_PX, TOOLTIP_CONNECTOR_PADDING_PX, TOOLTIP_OFFSET_PX, TOOLTIP_TOTAL_BORDER_WIDTH_PX, TOOLTIP_VIEWPORT_PADDING_PX, } from './types.js';
|
|
2
2
|
const MAX_EXHAUSTIVE_HORIZONTAL_ROW_LAYOUTS = 10;
|
|
3
3
|
const HORIZONTAL_ROW_ORDER_FLIP_PENALTY_PX = 128;
|
|
4
|
+
const AUTO_BAR_TOOLTIP_CONNECTOR_MIN_PATH_LENGTH_PX = 8;
|
|
4
5
|
export function getSplitTooltipViewportBounds() {
|
|
5
6
|
const visualViewport = window.visualViewport;
|
|
6
7
|
const viewportLeft = window.scrollX + (visualViewport?.offsetLeft ?? 0);
|
|
@@ -98,13 +99,16 @@ export function getAnchoredTooltipPosition(anchor, target, tooltipWidth, tooltip
|
|
|
98
99
|
};
|
|
99
100
|
}
|
|
100
101
|
export function resolveTooltipConnectorLayout(arrowEdge, tooltipLeft, tooltipTop, tooltipWidth, tooltipHeight, targetX, targetY, anchor) {
|
|
101
|
-
const
|
|
102
|
-
const
|
|
102
|
+
const boxArrowPosition = resolveTooltipBoxArrowPosition(arrowEdge, tooltipLeft, tooltipTop, tooltipWidth, tooltipHeight, targetX, targetY);
|
|
103
|
+
const arrowTip = resolveTooltipArrowTip(arrowEdge, boxArrowPosition.x, boxArrowPosition.y, TOOLTIP_BOX_ARROW_LENGTH_PX);
|
|
104
|
+
const arrowTipX = tooltipLeft + arrowTip.x;
|
|
105
|
+
const arrowTipY = tooltipTop + arrowTip.y;
|
|
106
|
+
const connectorTarget = resolveTooltipConnectorTarget(arrowEdge, targetX, targetY, arrowTipX, arrowTipY, anchor);
|
|
107
|
+
const localTargetX = connectorTarget.x - tooltipLeft;
|
|
108
|
+
const localTargetY = connectorTarget.y - tooltipTop;
|
|
103
109
|
if (!Number.isFinite(localTargetX) || !Number.isFinite(localTargetY)) {
|
|
104
110
|
return null;
|
|
105
111
|
}
|
|
106
|
-
const boxArrowPosition = resolveTooltipBoxArrowPosition(arrowEdge, tooltipLeft, tooltipTop, tooltipWidth, tooltipHeight, targetX, targetY);
|
|
107
|
-
const arrowTip = resolveTooltipArrowTip(arrowEdge, boxArrowPosition.x, boxArrowPosition.y, TOOLTIP_BOX_ARROW_LENGTH_PX);
|
|
108
112
|
const minX = Math.min(arrowTip.x, localTargetX) - TOOLTIP_CONNECTOR_PADDING_PX;
|
|
109
113
|
const maxX = Math.max(arrowTip.x, localTargetX) + TOOLTIP_CONNECTOR_PADDING_PX;
|
|
110
114
|
const minY = Math.min(arrowTip.y, localTargetY) - TOOLTIP_CONNECTOR_PADDING_PX;
|
|
@@ -117,9 +121,7 @@ export function resolveTooltipConnectorLayout(arrowEdge, tooltipLeft, tooltipTop
|
|
|
117
121
|
const startY = arrowTip.y - minY;
|
|
118
122
|
const endX = localTargetX - minX;
|
|
119
123
|
const endY = localTargetY - minY;
|
|
120
|
-
const
|
|
121
|
-
const arrowTipY = tooltipTop + arrowTip.y;
|
|
122
|
-
const connectorPath = resolveTooltipConnectorPath(arrowEdge, startX, startY, endX, endY, arrowTipX, arrowTipY, anchor);
|
|
124
|
+
const connectorPath = resolveTooltipConnectorPath(arrowEdge, startX, startY, endX, endY, arrowTipX, arrowTipY, anchor, connectorTarget.preferDirectPath);
|
|
123
125
|
if (!hasFiniteNumbers(width, height, boxX, boxY, startX, startY, endX, endY)) {
|
|
124
126
|
return null;
|
|
125
127
|
}
|
|
@@ -134,6 +136,43 @@ export function resolveTooltipConnectorLayout(arrowEdge, tooltipLeft, tooltipTop
|
|
|
134
136
|
arrowY: boxArrowPosition.y,
|
|
135
137
|
};
|
|
136
138
|
}
|
|
139
|
+
function resolveTooltipConnectorTarget(arrowEdge, targetX, targetY, arrowTipX, arrowTipY, anchor) {
|
|
140
|
+
if (isPointTooltipAnchor(anchor)) {
|
|
141
|
+
return { x: targetX, y: targetY, preferDirectPath: false };
|
|
142
|
+
}
|
|
143
|
+
if (arrowEdge === 'left') {
|
|
144
|
+
return {
|
|
145
|
+
x: anchor.right,
|
|
146
|
+
y: clampTooltipCoordinate(arrowTipY, anchor.top, anchor.bottom),
|
|
147
|
+
preferDirectPath: true,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (arrowEdge === 'right') {
|
|
151
|
+
return {
|
|
152
|
+
x: anchor.left,
|
|
153
|
+
y: clampTooltipCoordinate(arrowTipY, anchor.top, anchor.bottom),
|
|
154
|
+
preferDirectPath: true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (arrowEdge === 'top') {
|
|
158
|
+
return {
|
|
159
|
+
x: clampTooltipCoordinate(arrowTipX, anchor.left, anchor.right),
|
|
160
|
+
y: anchor.bottom,
|
|
161
|
+
preferDirectPath: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
x: clampTooltipCoordinate(arrowTipX, anchor.left, anchor.right),
|
|
166
|
+
y: anchor.top,
|
|
167
|
+
preferDirectPath: true,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function isPointTooltipAnchor(anchor) {
|
|
171
|
+
return (Math.abs(anchor.left - anchor.right) <=
|
|
172
|
+
TOOLTIP_CONNECTOR_ALIGNMENT_TOLERANCE_PX &&
|
|
173
|
+
Math.abs(anchor.top - anchor.bottom) <=
|
|
174
|
+
TOOLTIP_CONNECTOR_ALIGNMENT_TOLERANCE_PX);
|
|
175
|
+
}
|
|
137
176
|
export function resolveTooltipArrowPosition(arrowEdge, boxX, boxY, length, halfHeight) {
|
|
138
177
|
switch (arrowEdge) {
|
|
139
178
|
case 'left':
|
|
@@ -210,17 +249,26 @@ function getTooltipConnectorOffset(start, size, target) {
|
|
|
210
249
|
const preferredOffset = target - start;
|
|
211
250
|
return Math.max(minOffset, Math.min(preferredOffset, maxOffset));
|
|
212
251
|
}
|
|
213
|
-
function resolveTooltipConnectorPath(arrowEdge, startX, startY, endX, endY, arrowTipX, arrowTipY, anchor) {
|
|
252
|
+
function resolveTooltipConnectorPath(arrowEdge, startX, startY, endX, endY, arrowTipX, arrowTipY, anchor, preferDirectPath) {
|
|
214
253
|
if (arrowEdge === 'left' || arrowEdge === 'right') {
|
|
215
|
-
|
|
254
|
+
const minPathLength = preferDirectPath
|
|
255
|
+
? AUTO_BAR_TOOLTIP_CONNECTOR_MIN_PATH_LENGTH_PX
|
|
256
|
+
: TOOLTIP_CONNECTOR_MIN_PATH_LENGTH_PX;
|
|
257
|
+
if (isTooltipArrowTipNearVerticalAnchorSpan(arrowTipY, anchor, minPathLength)) {
|
|
216
258
|
return '';
|
|
217
259
|
}
|
|
260
|
+
if (preferDirectPath) {
|
|
261
|
+
return `M ${startX},${startY} L ${endX},${endY}`;
|
|
262
|
+
}
|
|
218
263
|
const elbowX = startX + (endX - startX) * TOOLTIP_CONNECTOR_ELBOW_RATIO;
|
|
219
264
|
return `M ${startX},${startY} L ${elbowX},${startY} L ${endX},${endY}`;
|
|
220
265
|
}
|
|
221
266
|
if (isTooltipArrowTipInsideHorizontalAnchorSpan(arrowTipX, anchor)) {
|
|
222
267
|
return '';
|
|
223
268
|
}
|
|
269
|
+
if (preferDirectPath) {
|
|
270
|
+
return `M ${startX},${startY} L ${endX},${endY}`;
|
|
271
|
+
}
|
|
224
272
|
const elbowY = startY + (endY - startY) * TOOLTIP_CONNECTOR_ELBOW_RATIO;
|
|
225
273
|
return `M ${startX},${startY} L ${startX},${elbowY} L ${endX},${endY}`;
|
|
226
274
|
}
|
|
@@ -232,12 +280,12 @@ function isTooltipArrowTipInsideVerticalAnchorSpan(arrowTipY, anchor) {
|
|
|
232
280
|
return (arrowTipY >= anchor.top - TOOLTIP_CONNECTOR_ALIGNMENT_TOLERANCE_PX &&
|
|
233
281
|
arrowTipY <= anchor.bottom + TOOLTIP_CONNECTOR_ALIGNMENT_TOLERANCE_PX);
|
|
234
282
|
}
|
|
235
|
-
function isTooltipArrowTipNearVerticalAnchorSpan(arrowTipY, anchor) {
|
|
283
|
+
function isTooltipArrowTipNearVerticalAnchorSpan(arrowTipY, anchor, minPathLength = TOOLTIP_CONNECTOR_MIN_PATH_LENGTH_PX) {
|
|
236
284
|
if (isTooltipArrowTipInsideVerticalAnchorSpan(arrowTipY, anchor)) {
|
|
237
285
|
return true;
|
|
238
286
|
}
|
|
239
|
-
return (arrowTipY >= anchor.top -
|
|
240
|
-
arrowTipY <= anchor.bottom +
|
|
287
|
+
return (arrowTipY >= anchor.top - minPathLength &&
|
|
288
|
+
arrowTipY <= anchor.bottom + minPathLength);
|
|
241
289
|
}
|
|
242
290
|
function resolveTooltipArrowTip(arrowEdge, boxX, boxY, length) {
|
|
243
291
|
if (arrowEdge === 'left' || arrowEdge === 'right') {
|
|
@@ -288,6 +336,7 @@ function getCombinedSplitTooltipTarget(layouts) {
|
|
|
288
336
|
function getSideSplitTooltipLayouts(layouts) {
|
|
289
337
|
if (layouts.every((layout) => layout.targetMode === 'auto')) {
|
|
290
338
|
return [...layouts].sort((a, b) => a.anchor.top - b.anchor.top ||
|
|
339
|
+
a.anchor.bottom - b.anchor.bottom ||
|
|
291
340
|
a.anchor.left - b.anchor.left ||
|
|
292
341
|
a.order - b.order);
|
|
293
342
|
}
|
|
@@ -662,6 +711,15 @@ function getSidePlacementCandidates(layout, placedLayouts, allLayouts, bounds) {
|
|
|
662
711
|
];
|
|
663
712
|
if (layout.targetMode === 'auto') {
|
|
664
713
|
const connectorlessRange = getSideConnectorlessTopRange(layout);
|
|
714
|
+
const exactConnectorlessRange = getExactSideConnectorlessTopRange(layout);
|
|
715
|
+
candidates.push({
|
|
716
|
+
top: exactConnectorlessRange.min,
|
|
717
|
+
arrowEdge: layout.arrowEdge,
|
|
718
|
+
});
|
|
719
|
+
candidates.push({
|
|
720
|
+
top: exactConnectorlessRange.max,
|
|
721
|
+
arrowEdge: layout.arrowEdge,
|
|
722
|
+
});
|
|
665
723
|
candidates.push({
|
|
666
724
|
top: connectorlessRange.min,
|
|
667
725
|
arrowEdge: layout.arrowEdge,
|
|
@@ -750,6 +808,17 @@ function getFixedPointSideSpreadPreferredTop(layout, direction) {
|
|
|
750
808
|
return layout.targetY - TOOLTIP_CONNECTOR_INSET_PX;
|
|
751
809
|
}
|
|
752
810
|
function getSideConnectorlessTopRange(layout) {
|
|
811
|
+
const nearPathPadding = layout.targetMode === 'auto'
|
|
812
|
+
? AUTO_BAR_TOOLTIP_CONNECTOR_MIN_PATH_LENGTH_PX
|
|
813
|
+
: 0;
|
|
814
|
+
return {
|
|
815
|
+
min: layout.anchor.top -
|
|
816
|
+
getTooltipConnectorMaxOffset(layout.height) -
|
|
817
|
+
nearPathPadding,
|
|
818
|
+
max: layout.anchor.bottom - TOOLTIP_CONNECTOR_INSET_PX + nearPathPadding,
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
function getExactSideConnectorlessTopRange(layout) {
|
|
753
822
|
return {
|
|
754
823
|
min: layout.anchor.top - getTooltipConnectorMaxOffset(layout.height),
|
|
755
824
|
max: layout.anchor.bottom - TOOLTIP_CONNECTOR_INSET_PX,
|
|
@@ -757,7 +826,9 @@ function getSideConnectorlessTopRange(layout) {
|
|
|
757
826
|
}
|
|
758
827
|
function isSidePlacementConnectorless(candidate, layout) {
|
|
759
828
|
if (layout.targetMode === 'auto') {
|
|
760
|
-
|
|
829
|
+
const connectorlessRange = getSideConnectorlessTopRange(layout);
|
|
830
|
+
return (candidate.top >= connectorlessRange.min &&
|
|
831
|
+
candidate.top <= connectorlessRange.max);
|
|
761
832
|
}
|
|
762
833
|
const arrowTipY = getSidePlacementArrowTipY(candidate.top, layout);
|
|
763
834
|
return (arrowTipY >=
|
package/dist/tooltip/types.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { Scatter } from '../scatter.js';
|
|
|
6
6
|
import type { TooltipTransitionConfig } from '../types.js';
|
|
7
7
|
export declare const TOOLTIP_OFFSET_PX = 12;
|
|
8
8
|
export declare const TOOLTIP_VIEWPORT_PADDING_PX = 10;
|
|
9
|
-
export declare const TOOLTIP_CONNECTOR_INSET_PX =
|
|
9
|
+
export declare const TOOLTIP_CONNECTOR_INSET_PX = 10;
|
|
10
10
|
export declare const TOOLTIP_CONNECTOR_MIN_PATH_LENGTH_PX = 16;
|
|
11
11
|
export declare const TOOLTIP_CONNECTOR_PADDING_PX = 4;
|
|
12
12
|
export declare const TOOLTIP_CONNECTOR_ELBOW_RATIO = 0.45;
|
|
@@ -20,7 +20,7 @@ export declare const TOOLTIP_ARROW_FILL_Z_INDEX = 5;
|
|
|
20
20
|
export declare const TOOLTIP_BODY_Z_INDEX = 6;
|
|
21
21
|
export declare const TOOLTIP_TOTAL_BORDER_WIDTH_PX: number;
|
|
22
22
|
export declare const TOOLTIP_ROOT_Z_INDEX = 30;
|
|
23
|
-
export declare const SPLIT_TOOLTIP_GAP_PX =
|
|
23
|
+
export declare const SPLIT_TOOLTIP_GAP_PX = 4;
|
|
24
24
|
export declare const DEFAULT_TOOLTIP_MAX_WIDTH_PX = 280;
|
|
25
25
|
export declare const DEFAULT_TOOLTIP_TRANSITION: Required<TooltipTransitionConfig>;
|
|
26
26
|
export declare const TOOLTIP_HIDDEN_TRANSFORM = "translateY(2px)";
|
package/dist/tooltip/types.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const TOOLTIP_OFFSET_PX = 12;
|
|
2
2
|
export const TOOLTIP_VIEWPORT_PADDING_PX = 10;
|
|
3
|
-
export const TOOLTIP_CONNECTOR_INSET_PX =
|
|
3
|
+
export const TOOLTIP_CONNECTOR_INSET_PX = 10;
|
|
4
4
|
export const TOOLTIP_CONNECTOR_MIN_PATH_LENGTH_PX = 16;
|
|
5
5
|
export const TOOLTIP_CONNECTOR_PADDING_PX = 4;
|
|
6
6
|
export const TOOLTIP_CONNECTOR_ELBOW_RATIO = 0.45;
|
|
@@ -14,7 +14,7 @@ export const TOOLTIP_ARROW_FILL_Z_INDEX = 5;
|
|
|
14
14
|
export const TOOLTIP_BODY_Z_INDEX = 6;
|
|
15
15
|
export const TOOLTIP_TOTAL_BORDER_WIDTH_PX = TOOLTIP_BORDER_WIDTH_PX * 2;
|
|
16
16
|
export const TOOLTIP_ROOT_Z_INDEX = 30;
|
|
17
|
-
export const SPLIT_TOOLTIP_GAP_PX =
|
|
17
|
+
export const SPLIT_TOOLTIP_GAP_PX = 4;
|
|
18
18
|
export const DEFAULT_TOOLTIP_MAX_WIDTH_PX = 280;
|
|
19
19
|
export const DEFAULT_TOOLTIP_TRANSITION = {
|
|
20
20
|
show: false,
|
|
@@ -517,12 +517,36 @@ function getClosestSingleTooltipCandidate(candidates, request) {
|
|
|
517
517
|
!Number.isFinite(request.pointerY)) {
|
|
518
518
|
return candidates[0];
|
|
519
519
|
}
|
|
520
|
+
const touchedBarCandidate = getTouchedBarTooltipCandidate(candidates, request);
|
|
521
|
+
if (touchedBarCandidate) {
|
|
522
|
+
return touchedBarCandidate;
|
|
523
|
+
}
|
|
520
524
|
return candidates.reduce((closest, candidate) => {
|
|
521
525
|
const closestDistance = getSingleTooltipDistance(closest, request);
|
|
522
526
|
const candidateDistance = getSingleTooltipDistance(candidate, request);
|
|
523
527
|
return candidateDistance < closestDistance ? candidate : closest;
|
|
524
528
|
});
|
|
525
529
|
}
|
|
530
|
+
function getTouchedBarTooltipCandidate(candidates, request) {
|
|
531
|
+
const touchedBarCandidates = candidates.filter((candidate) => {
|
|
532
|
+
return (candidate.series.type === 'bar' &&
|
|
533
|
+
isPointerInsideAnchor(request, candidate.anchor));
|
|
534
|
+
});
|
|
535
|
+
return touchedBarCandidates[touchedBarCandidates.length - 1] ?? null;
|
|
536
|
+
}
|
|
537
|
+
function isPointerInsideAnchor(request, anchor) {
|
|
538
|
+
const { pointerX, pointerY } = request;
|
|
539
|
+
if (pointerX === undefined ||
|
|
540
|
+
pointerY === undefined ||
|
|
541
|
+
!Number.isFinite(pointerX) ||
|
|
542
|
+
!Number.isFinite(pointerY)) {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
return (pointerX >= anchor.left &&
|
|
546
|
+
pointerX <= anchor.right &&
|
|
547
|
+
pointerY >= anchor.top &&
|
|
548
|
+
pointerY <= anchor.bottom);
|
|
549
|
+
}
|
|
526
550
|
function getSingleTooltipDistance(candidate, request) {
|
|
527
551
|
const deltaX = (request.pointerX ?? 0) - candidate.target.x;
|
|
528
552
|
const deltaY = (request.pointerY ?? 0) - candidate.target.y;
|
package/dist/types.d.ts
CHANGED
|
@@ -330,10 +330,13 @@ export type TooltipConfigBase = {
|
|
|
330
330
|
export type TooltipConfig = TooltipConfigBase & {
|
|
331
331
|
exportHooks?: ExportHooks<TooltipConfigBase>;
|
|
332
332
|
};
|
|
333
|
+
export type HorizontalAlignment = 'left' | 'center' | 'right';
|
|
333
334
|
export type LegendConfigBase = {
|
|
334
335
|
mode?: LegendMode;
|
|
335
336
|
position?: 'bottom';
|
|
336
337
|
disconnectedTarget?: string | HTMLElement;
|
|
338
|
+
align?: HorizontalAlignment;
|
|
339
|
+
textAlign?: HorizontalAlignment;
|
|
337
340
|
marginTop?: number;
|
|
338
341
|
marginBottom?: number;
|
|
339
342
|
paddingX?: number;
|
|
@@ -355,7 +358,7 @@ export type LegendItem = {
|
|
|
355
358
|
visible: boolean;
|
|
356
359
|
};
|
|
357
360
|
export type TextPosition = 'top' | 'bottom';
|
|
358
|
-
export type TextAlign =
|
|
361
|
+
export type TextAlign = HorizontalAlignment;
|
|
359
362
|
export type TextVariantStyle = {
|
|
360
363
|
fontSize?: number;
|
|
361
364
|
fontWeight?: string;
|
package/docs/components.md
CHANGED
|
@@ -211,6 +211,8 @@ new Legend({
|
|
|
211
211
|
mode?: 'inline' | 'disconnected' | 'hidden', // Rendering mode (default: 'inline')
|
|
212
212
|
position?: 'bottom', // Position (currently only 'bottom')
|
|
213
213
|
disconnectedTarget?: string | HTMLElement, // External mount target when mode is 'disconnected'
|
|
214
|
+
align?: 'left' | 'center' | 'right', // Legend block alignment (default: 'center')
|
|
215
|
+
textAlign?: 'left' | 'center' | 'right', // Wrapped row alignment within the legend block (default: 'center')
|
|
214
216
|
marginTop?: number, // Space above legend (default: 20)
|
|
215
217
|
marginBottom?: number, // Space below legend (default: 10)
|
|
216
218
|
paddingX?: number, // Horizontal inset for legend layout (default: theme.legend.paddingX)
|
|
@@ -220,7 +222,10 @@ new Legend({
|
|
|
220
222
|
```
|
|
221
223
|
|
|
222
224
|
Legend items automatically wrap to new rows when they do not fit available
|
|
223
|
-
width.
|
|
225
|
+
width. The widest wrapped row defines the legend block. Use `align` to position
|
|
226
|
+
that block and `textAlign` to align rows inside it, for example
|
|
227
|
+
`{ align: 'center', textAlign: 'left' }` for a centered legend with
|
|
228
|
+
left-aligned wrapped rows.
|
|
224
229
|
|
|
225
230
|
- `inline`: current default behavior (legend rendered inside the chart SVG)
|
|
226
231
|
- `disconnected`: legend rendered in a separate SVG outside chart layout
|
package/package.json
CHANGED