@internetstiftelsen/charts 0.15.0 → 0.16.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 +4 -0
- package/dist/theme.js +6 -0
- package/dist/tooltip/dom.d.ts +56 -0
- package/dist/tooltip/dom.js +438 -0
- package/dist/tooltip/geometry.d.ts +18 -0
- package/dist/tooltip/geometry.js +395 -0
- package/dist/tooltip/types.d.ts +77 -0
- package/dist/tooltip/types.js +24 -0
- package/dist/tooltip/xy-interaction.d.ts +33 -0
- package/dist/tooltip/xy-interaction.js +608 -0
- package/dist/tooltip.d.ts +4 -74
- package/dist/tooltip.js +41 -1444
- package/dist/types.d.ts +2 -0
- 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 +16 -19
- package/docs/xy-chart.md +3 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ A framework-agnostic, composable charting library built on D3.js with TypeScript
|
|
|
11
11
|
- **Divergent Bar Support** - Bar charts automatically render from zero and diverge around `0` for mixed positive/negative values
|
|
12
12
|
- **Mirrored Bar Sides** - Horizontal bars can mirror a series to the left for population-pyramid style charts without changing source data
|
|
13
13
|
- **Custom Value Labels** - XY, pie, donut, and gauge charts support configurable labels with formatters, max-width overflow behavior, and forced rendering when labels would otherwise be hidden
|
|
14
|
+
- **Axis Label Overflow** - X/Y tick labels and grouped X-axis labels support max-width overflow behavior
|
|
14
15
|
- **Optional XY Animation** - Animate XY series on first render and `chart.update(...)` with `animate`
|
|
15
16
|
- **Optional Radial Animation** - Animate pie and donut segments on first render and `chart.update(...)` with `animate`
|
|
16
17
|
- **Optional Gauge Animation** - Animate gauge value transitions with `gauge.animate`
|
|
@@ -349,6 +350,9 @@ const chart = new XYChart({
|
|
|
349
350
|
});
|
|
350
351
|
```
|
|
351
352
|
|
|
353
|
+
The exported `defaultResponsiveConfig` also switches tooltip components to
|
|
354
|
+
`mode: 'shared'` at its `sm` breakpoint for compact XY charts.
|
|
355
|
+
|
|
352
356
|
## Word Cloud
|
|
353
357
|
|
|
354
358
|
```javascript
|
package/dist/theme.js
CHANGED
|
@@ -325,6 +325,12 @@ export const defaultResponsiveConfig = {
|
|
|
325
325
|
tooltip: { fontSize: 11 },
|
|
326
326
|
valueLabel: { fontSize: 10 },
|
|
327
327
|
},
|
|
328
|
+
components: [
|
|
329
|
+
{
|
|
330
|
+
match: { type: 'tooltip' },
|
|
331
|
+
override: { mode: 'shared' },
|
|
332
|
+
},
|
|
333
|
+
],
|
|
328
334
|
},
|
|
329
335
|
md: {
|
|
330
336
|
minWidth: 480,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ChartTheme, TooltipTransitionConfig } from '../types.js';
|
|
2
|
+
import { type TooltipAnchor, type TooltipArrowEdge, type TooltipDivSelection } from './types.js';
|
|
3
|
+
type TooltipDomConfig = {
|
|
4
|
+
id: string;
|
|
5
|
+
splitTooltipOwner: string;
|
|
6
|
+
maxWidth: number;
|
|
7
|
+
transition: Required<TooltipTransitionConfig>;
|
|
8
|
+
};
|
|
9
|
+
export declare class TooltipDom {
|
|
10
|
+
private readonly id;
|
|
11
|
+
private readonly splitTooltipOwner;
|
|
12
|
+
private readonly maxWidth;
|
|
13
|
+
private readonly transition;
|
|
14
|
+
private readonly tooltipStyleKeys;
|
|
15
|
+
private readonly tooltipTransitionFrameIds;
|
|
16
|
+
private tooltipDiv;
|
|
17
|
+
private tooltipTheme;
|
|
18
|
+
constructor(config: TooltipDomConfig);
|
|
19
|
+
initialize(theme: ChartTheme): void;
|
|
20
|
+
getRootTooltip(): TooltipDivSelection | null;
|
|
21
|
+
setContent(content: string): void;
|
|
22
|
+
getBounds(): DOMRect | null;
|
|
23
|
+
showAt(left: number, top: number): void;
|
|
24
|
+
hide(): void;
|
|
25
|
+
cleanup(): void;
|
|
26
|
+
measureTooltip(tooltip: TooltipDivSelection, content: string): {
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
} | null;
|
|
30
|
+
renderTooltipWithConnector(tooltip: TooltipDivSelection, arrowEdge: TooltipArrowEdge, left: number, top: number, tooltipWidth: number, tooltipHeight: number, targetX: number, targetY: number, anchor: TooltipAnchor): void;
|
|
31
|
+
renderTooltipWithoutConnector(tooltip: TooltipDivSelection, left: number, top: number): void;
|
|
32
|
+
hideTooltipSelection(tooltip: TooltipDivSelection): void;
|
|
33
|
+
getSplitTooltip(index: number, theme: ChartTheme): TooltipDivSelection;
|
|
34
|
+
hideSplitTooltips(): void;
|
|
35
|
+
hideUnusedSplitTooltips(visibleTooltips: TooltipDivSelection[]): void;
|
|
36
|
+
private applyTooltipStylesIfNeeded;
|
|
37
|
+
private getTooltipStyleKey;
|
|
38
|
+
private writeTooltipStyles;
|
|
39
|
+
private showTooltipAt;
|
|
40
|
+
private showTooltipSelection;
|
|
41
|
+
private hideTooltipElement;
|
|
42
|
+
private getTooltipTransitionStyle;
|
|
43
|
+
private isTooltipVisible;
|
|
44
|
+
private getTooltipPosition;
|
|
45
|
+
private hasVisibleSlideOffset;
|
|
46
|
+
private slideTooltipFromOffset;
|
|
47
|
+
private requestTooltipTransitionFrame;
|
|
48
|
+
private cancelTooltipTransitionFrame;
|
|
49
|
+
private setTooltipMarkup;
|
|
50
|
+
private appendTooltipConnector;
|
|
51
|
+
private appendTooltipArrow;
|
|
52
|
+
private appendTooltipArrowTriangle;
|
|
53
|
+
private removeSplitTooltips;
|
|
54
|
+
private removeRootTooltip;
|
|
55
|
+
}
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { select } from 'd3';
|
|
2
|
+
import { resolveTooltipArrowPosition, resolveTooltipConnectorLayout, } from './geometry.js';
|
|
3
|
+
import { TOOLTIP_ARROW_BORDER_Z_INDEX, TOOLTIP_ARROW_FILL_Z_INDEX, TOOLTIP_BODY_Z_INDEX, TOOLTIP_BORDER_WIDTH_PX, TOOLTIP_BOX_ARROW_HALF_HEIGHT_PX, TOOLTIP_BOX_ARROW_LENGTH_PX, TOOLTIP_CONNECTOR_Z_INDEX, TOOLTIP_HIDDEN_TRANSFORM, TOOLTIP_ROOT_Z_INDEX, TOOLTIP_VISIBLE_TRANSFORM, } from './types.js';
|
|
4
|
+
export class TooltipDom {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
Object.defineProperty(this, "id", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
writable: true,
|
|
10
|
+
value: void 0
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(this, "splitTooltipOwner", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
configurable: true,
|
|
15
|
+
writable: true,
|
|
16
|
+
value: void 0
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(this, "maxWidth", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
configurable: true,
|
|
21
|
+
writable: true,
|
|
22
|
+
value: void 0
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(this, "transition", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
configurable: true,
|
|
27
|
+
writable: true,
|
|
28
|
+
value: void 0
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(this, "tooltipStyleKeys", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
configurable: true,
|
|
33
|
+
writable: true,
|
|
34
|
+
value: new WeakMap()
|
|
35
|
+
});
|
|
36
|
+
Object.defineProperty(this, "tooltipTransitionFrameIds", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
configurable: true,
|
|
39
|
+
writable: true,
|
|
40
|
+
value: new WeakMap()
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(this, "tooltipDiv", {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
configurable: true,
|
|
45
|
+
writable: true,
|
|
46
|
+
value: null
|
|
47
|
+
});
|
|
48
|
+
Object.defineProperty(this, "tooltipTheme", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
configurable: true,
|
|
51
|
+
writable: true,
|
|
52
|
+
value: null
|
|
53
|
+
});
|
|
54
|
+
this.id = config.id;
|
|
55
|
+
this.splitTooltipOwner = config.splitTooltipOwner;
|
|
56
|
+
this.maxWidth = config.maxWidth;
|
|
57
|
+
this.transition = config.transition;
|
|
58
|
+
}
|
|
59
|
+
initialize(theme) {
|
|
60
|
+
const existingTooltip = select(`#${this.id}`);
|
|
61
|
+
const tooltip = existingTooltip.empty()
|
|
62
|
+
? select('body')
|
|
63
|
+
.append('div')
|
|
64
|
+
.attr('class', 'chart-tooltip')
|
|
65
|
+
.attr('id', this.id)
|
|
66
|
+
: existingTooltip;
|
|
67
|
+
this.removeSplitTooltips();
|
|
68
|
+
this.applyTooltipStylesIfNeeded(tooltip, theme);
|
|
69
|
+
this.tooltipDiv = tooltip;
|
|
70
|
+
this.hideTooltipSelection(tooltip);
|
|
71
|
+
}
|
|
72
|
+
getRootTooltip() {
|
|
73
|
+
return this.tooltipDiv;
|
|
74
|
+
}
|
|
75
|
+
setContent(content) {
|
|
76
|
+
if (!this.tooltipDiv) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.setTooltipMarkup(this.tooltipDiv, content);
|
|
80
|
+
}
|
|
81
|
+
getBounds() {
|
|
82
|
+
const node = this.tooltipDiv?.node();
|
|
83
|
+
if (!node) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return node.getBoundingClientRect();
|
|
87
|
+
}
|
|
88
|
+
showAt(left, top) {
|
|
89
|
+
if (!this.tooltipDiv) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!Number.isFinite(left) || !Number.isFinite(top)) {
|
|
93
|
+
this.hide();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.showTooltipAt(this.tooltipDiv, left, top);
|
|
97
|
+
}
|
|
98
|
+
hide() {
|
|
99
|
+
const tooltip = this.tooltipDiv ?? select(`#${this.id}`);
|
|
100
|
+
if (!tooltip.empty()) {
|
|
101
|
+
this.hideTooltipSelection(tooltip);
|
|
102
|
+
}
|
|
103
|
+
this.hideSplitTooltips();
|
|
104
|
+
}
|
|
105
|
+
cleanup() {
|
|
106
|
+
this.removeRootTooltip();
|
|
107
|
+
this.removeSplitTooltips();
|
|
108
|
+
this.tooltipDiv = null;
|
|
109
|
+
}
|
|
110
|
+
measureTooltip(tooltip, content) {
|
|
111
|
+
this.setTooltipMarkup(tooltip, content);
|
|
112
|
+
const tooltipNode = tooltip.node();
|
|
113
|
+
if (!tooltipNode) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (!this.isTooltipVisible(tooltipNode)) {
|
|
117
|
+
tooltip.style('left', '-9999px').style('top', '-9999px');
|
|
118
|
+
this.hideTooltipSelection(tooltip);
|
|
119
|
+
}
|
|
120
|
+
const tooltipRect = tooltipNode.getBoundingClientRect();
|
|
121
|
+
if (!Number.isFinite(tooltipRect.width) ||
|
|
122
|
+
!Number.isFinite(tooltipRect.height)) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
width: tooltipRect.width,
|
|
127
|
+
height: tooltipRect.height,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
renderTooltipWithConnector(tooltip, arrowEdge, left, top, tooltipWidth, tooltipHeight, targetX, targetY, anchor) {
|
|
131
|
+
if (!Number.isFinite(left) ||
|
|
132
|
+
!Number.isFinite(top) ||
|
|
133
|
+
!Number.isFinite(targetX) ||
|
|
134
|
+
!Number.isFinite(targetY)) {
|
|
135
|
+
this.hideTooltipSelection(tooltip);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const connectorLayout = resolveTooltipConnectorLayout(arrowEdge, left, top, tooltipWidth, tooltipHeight, targetX, targetY, anchor);
|
|
139
|
+
if (!connectorLayout) {
|
|
140
|
+
this.hideTooltipSelection(tooltip);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.appendTooltipConnector(tooltip, connectorLayout);
|
|
144
|
+
this.appendTooltipArrow(tooltip, connectorLayout);
|
|
145
|
+
this.showTooltipAt(tooltip, left, top);
|
|
146
|
+
}
|
|
147
|
+
renderTooltipWithoutConnector(tooltip, left, top) {
|
|
148
|
+
if (!Number.isFinite(left) || !Number.isFinite(top)) {
|
|
149
|
+
this.hideTooltipSelection(tooltip);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
this.showTooltipAt(tooltip, left, top);
|
|
153
|
+
}
|
|
154
|
+
hideTooltipSelection(tooltip) {
|
|
155
|
+
const node = tooltip.node();
|
|
156
|
+
if (!node) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
this.hideTooltipElement(node);
|
|
160
|
+
}
|
|
161
|
+
getSplitTooltip(index, theme) {
|
|
162
|
+
const tooltipId = `${this.splitTooltipOwner}-${index}`;
|
|
163
|
+
const existingTooltip = select(`#${tooltipId}`);
|
|
164
|
+
const tooltip = existingTooltip.empty()
|
|
165
|
+
? select('body')
|
|
166
|
+
.append('div')
|
|
167
|
+
.attr('class', 'chart-tooltip chart-tooltip--split')
|
|
168
|
+
.attr('id', tooltipId)
|
|
169
|
+
.attr('data-chart-tooltip-owner', this.splitTooltipOwner)
|
|
170
|
+
.attr('data-chart-tooltip-index', String(index))
|
|
171
|
+
: existingTooltip;
|
|
172
|
+
this.applyTooltipStylesIfNeeded(tooltip, theme);
|
|
173
|
+
return tooltip;
|
|
174
|
+
}
|
|
175
|
+
hideSplitTooltips() {
|
|
176
|
+
document
|
|
177
|
+
.querySelectorAll(`[data-chart-tooltip-owner="${this.splitTooltipOwner}"]`)
|
|
178
|
+
.forEach((node) => {
|
|
179
|
+
this.hideTooltipElement(node);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
hideUnusedSplitTooltips(visibleTooltips) {
|
|
183
|
+
const visibleNodes = new Set(visibleTooltips
|
|
184
|
+
.map((tooltip) => tooltip.node())
|
|
185
|
+
.filter((node) => Boolean(node)));
|
|
186
|
+
document
|
|
187
|
+
.querySelectorAll(`[data-chart-tooltip-owner="${this.splitTooltipOwner}"]`)
|
|
188
|
+
.forEach((node) => {
|
|
189
|
+
if (visibleNodes.has(node)) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.hideTooltipElement(node);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
applyTooltipStylesIfNeeded(tooltip, theme) {
|
|
196
|
+
const node = tooltip.node();
|
|
197
|
+
if (!node) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const styleKey = this.getTooltipStyleKey(theme);
|
|
201
|
+
if (this.tooltipStyleKeys.get(node) === styleKey) {
|
|
202
|
+
this.tooltipTheme = theme.tooltip;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
this.tooltipStyleKeys.set(node, styleKey);
|
|
206
|
+
this.writeTooltipStyles(tooltip, theme);
|
|
207
|
+
}
|
|
208
|
+
getTooltipStyleKey(theme) {
|
|
209
|
+
return [
|
|
210
|
+
theme.tooltip.background,
|
|
211
|
+
theme.tooltip.border,
|
|
212
|
+
theme.tooltip.color,
|
|
213
|
+
theme.tooltip.fontFamily,
|
|
214
|
+
theme.tooltip.fontSize,
|
|
215
|
+
theme.tooltip.fontWeight,
|
|
216
|
+
this.maxWidth,
|
|
217
|
+
this.transition.show,
|
|
218
|
+
this.transition.duration,
|
|
219
|
+
this.transition.easing,
|
|
220
|
+
].join('|');
|
|
221
|
+
}
|
|
222
|
+
writeTooltipStyles(tooltip, theme) {
|
|
223
|
+
this.tooltipTheme = theme.tooltip;
|
|
224
|
+
tooltip
|
|
225
|
+
.style('position', 'absolute')
|
|
226
|
+
.style('background-color', theme.tooltip.background)
|
|
227
|
+
.style('border', `${TOOLTIP_BORDER_WIDTH_PX}px solid ${theme.tooltip.border}`)
|
|
228
|
+
.style('border-radius', '4px')
|
|
229
|
+
.style('padding', '8px')
|
|
230
|
+
.style('box-shadow', '0 2px 4px rgba(0,0,0,0.1)')
|
|
231
|
+
.style('color', theme.tooltip.color)
|
|
232
|
+
.style('font-family', theme.tooltip.fontFamily)
|
|
233
|
+
.style('font-size', `${theme.tooltip.fontSize}px`)
|
|
234
|
+
.style('font-weight', theme.tooltip.fontWeight)
|
|
235
|
+
.style('box-sizing', 'border-box')
|
|
236
|
+
.style('overflow-wrap', 'break-word')
|
|
237
|
+
.style('overflow', 'visible')
|
|
238
|
+
.style('isolation', 'isolate')
|
|
239
|
+
.style('pointer-events', 'none')
|
|
240
|
+
.style('z-index', String(TOOLTIP_ROOT_Z_INDEX));
|
|
241
|
+
tooltip.style('max-width', `${this.maxWidth}px`);
|
|
242
|
+
if (this.transition.show) {
|
|
243
|
+
tooltip
|
|
244
|
+
.style('transition', this.getTooltipTransitionStyle())
|
|
245
|
+
.style('will-change', 'opacity, transform');
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
tooltip
|
|
249
|
+
.style('opacity', null)
|
|
250
|
+
.style('transform', null)
|
|
251
|
+
.style('transition', null)
|
|
252
|
+
.style('will-change', null);
|
|
253
|
+
}
|
|
254
|
+
showTooltipAt(tooltip, left, top) {
|
|
255
|
+
const tooltipNode = tooltip.node();
|
|
256
|
+
const previousPosition = tooltipNode && this.isTooltipVisible(tooltipNode)
|
|
257
|
+
? this.getTooltipPosition(tooltipNode)
|
|
258
|
+
: null;
|
|
259
|
+
tooltip.style('left', `${left}px`).style('top', `${top}px`);
|
|
260
|
+
this.showTooltipSelection(tooltip, previousPosition
|
|
261
|
+
? {
|
|
262
|
+
x: previousPosition.left - left,
|
|
263
|
+
y: previousPosition.top - top,
|
|
264
|
+
}
|
|
265
|
+
: null);
|
|
266
|
+
}
|
|
267
|
+
showTooltipSelection(tooltip, slideOffset = null) {
|
|
268
|
+
tooltip.style('visibility', 'visible');
|
|
269
|
+
if (!this.transition.show) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
tooltip.style('opacity', '1');
|
|
273
|
+
const node = tooltip.node();
|
|
274
|
+
if (!node) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (!slideOffset || !this.hasVisibleSlideOffset(slideOffset)) {
|
|
278
|
+
this.cancelTooltipTransitionFrame(node);
|
|
279
|
+
tooltip.style('transform', TOOLTIP_VISIBLE_TRANSFORM);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
this.slideTooltipFromOffset(node, slideOffset);
|
|
283
|
+
}
|
|
284
|
+
hideTooltipElement(node) {
|
|
285
|
+
this.cancelTooltipTransitionFrame(node);
|
|
286
|
+
if (!this.transition.show) {
|
|
287
|
+
node.style.visibility = 'hidden';
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
node.style.visibility = 'visible';
|
|
291
|
+
node.style.opacity = '0';
|
|
292
|
+
node.style.transform = TOOLTIP_HIDDEN_TRANSFORM;
|
|
293
|
+
}
|
|
294
|
+
getTooltipTransitionStyle() {
|
|
295
|
+
return `opacity ${this.transition.duration}ms ${this.transition.easing}, transform ${this.transition.duration}ms ${this.transition.easing}`;
|
|
296
|
+
}
|
|
297
|
+
isTooltipVisible(node) {
|
|
298
|
+
return (node.style.visibility === 'visible' && node.style.opacity !== '0');
|
|
299
|
+
}
|
|
300
|
+
getTooltipPosition(node) {
|
|
301
|
+
const left = Number.parseFloat(node.style.left || '0');
|
|
302
|
+
const top = Number.parseFloat(node.style.top || '0');
|
|
303
|
+
if (!Number.isFinite(left) || !Number.isFinite(top)) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
return { left, top };
|
|
307
|
+
}
|
|
308
|
+
hasVisibleSlideOffset(offset) {
|
|
309
|
+
return Math.abs(offset.x) > 0.5 || Math.abs(offset.y) > 0.5;
|
|
310
|
+
}
|
|
311
|
+
slideTooltipFromOffset(node, offset) {
|
|
312
|
+
const transition = node.style.transition || this.getTooltipTransitionStyle();
|
|
313
|
+
this.cancelTooltipTransitionFrame(node);
|
|
314
|
+
node.style.setProperty('transition', 'none');
|
|
315
|
+
node.style.setProperty('transform', `translate(${offset.x}px, ${offset.y}px)`);
|
|
316
|
+
node.getBoundingClientRect();
|
|
317
|
+
node.style.setProperty('transition', transition);
|
|
318
|
+
const frameId = this.requestTooltipTransitionFrame(() => {
|
|
319
|
+
this.tooltipTransitionFrameIds.delete(node);
|
|
320
|
+
node.style.setProperty('transform', TOOLTIP_VISIBLE_TRANSFORM);
|
|
321
|
+
});
|
|
322
|
+
this.tooltipTransitionFrameIds.set(node, frameId);
|
|
323
|
+
}
|
|
324
|
+
requestTooltipTransitionFrame(callback) {
|
|
325
|
+
if (typeof window.requestAnimationFrame === 'function') {
|
|
326
|
+
return window.requestAnimationFrame(callback);
|
|
327
|
+
}
|
|
328
|
+
return window.setTimeout(() => {
|
|
329
|
+
callback(window.performance.now());
|
|
330
|
+
}, 16);
|
|
331
|
+
}
|
|
332
|
+
cancelTooltipTransitionFrame(node) {
|
|
333
|
+
const frameId = this.tooltipTransitionFrameIds.get(node);
|
|
334
|
+
if (frameId === undefined) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (typeof window.cancelAnimationFrame === 'function') {
|
|
338
|
+
window.cancelAnimationFrame(frameId);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
window.clearTimeout(frameId);
|
|
342
|
+
}
|
|
343
|
+
this.tooltipTransitionFrameIds.delete(node);
|
|
344
|
+
}
|
|
345
|
+
setTooltipMarkup(tooltip, content) {
|
|
346
|
+
tooltip.html(`<div data-chart-tooltip-body="true">${content}</div>`);
|
|
347
|
+
const body = tooltip.select('[data-chart-tooltip-body]');
|
|
348
|
+
if (body.empty()) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
body.style('position', 'relative').style('z-index', String(TOOLTIP_BODY_Z_INDEX));
|
|
352
|
+
}
|
|
353
|
+
appendTooltipConnector(tooltip, connectorLayout) {
|
|
354
|
+
const tooltipBorder = this.tooltipTheme?.border ?? '#dddddd';
|
|
355
|
+
const connector = tooltip
|
|
356
|
+
.append('svg')
|
|
357
|
+
.attr('data-chart-tooltip-connector', 'true')
|
|
358
|
+
.attr('data-chart-tooltip-arrow-edge', connectorLayout.arrowEdge)
|
|
359
|
+
.attr('aria-hidden', 'true')
|
|
360
|
+
.attr('width', connectorLayout.width)
|
|
361
|
+
.attr('height', connectorLayout.height)
|
|
362
|
+
.attr('viewBox', `0 0 ${connectorLayout.width} ${connectorLayout.height}`)
|
|
363
|
+
.style('position', 'absolute')
|
|
364
|
+
.style('left', `${connectorLayout.left}px`)
|
|
365
|
+
.style('top', `${connectorLayout.top}px`)
|
|
366
|
+
.style('pointer-events', 'none')
|
|
367
|
+
.style('overflow', 'visible')
|
|
368
|
+
.style('z-index', String(TOOLTIP_CONNECTOR_Z_INDEX));
|
|
369
|
+
connector
|
|
370
|
+
.append('path')
|
|
371
|
+
.attr('data-chart-tooltip-connector-path', 'true')
|
|
372
|
+
.attr('d', connectorLayout.path)
|
|
373
|
+
.attr('fill', 'none')
|
|
374
|
+
.attr('stroke', tooltipBorder)
|
|
375
|
+
.attr('stroke-width', 1.25)
|
|
376
|
+
.attr('stroke-linecap', 'round')
|
|
377
|
+
.attr('stroke-linejoin', 'round');
|
|
378
|
+
}
|
|
379
|
+
appendTooltipArrow(tooltip, connectorLayout) {
|
|
380
|
+
const tooltipBackground = this.tooltipTheme?.background ?? '#ffffff';
|
|
381
|
+
const tooltipBorder = this.tooltipTheme?.border ?? '#dddddd';
|
|
382
|
+
this.appendTooltipArrowTriangle(tooltip, connectorLayout, 'data-chart-tooltip-arrow', tooltipBorder, TOOLTIP_BOX_ARROW_LENGTH_PX, TOOLTIP_BOX_ARROW_HALF_HEIGHT_PX, TOOLTIP_ARROW_BORDER_Z_INDEX);
|
|
383
|
+
this.appendTooltipArrowTriangle(tooltip, connectorLayout, 'data-chart-tooltip-arrow-fill', tooltipBackground, TOOLTIP_BOX_ARROW_LENGTH_PX - 1, TOOLTIP_BOX_ARROW_HALF_HEIGHT_PX - 1, TOOLTIP_ARROW_FILL_Z_INDEX);
|
|
384
|
+
}
|
|
385
|
+
appendTooltipArrowTriangle(tooltip, connectorLayout, dataAttribute, color, length, halfHeight, zIndex) {
|
|
386
|
+
const position = resolveTooltipArrowPosition(connectorLayout.arrowEdge, connectorLayout.arrowX, connectorLayout.arrowY, length, halfHeight);
|
|
387
|
+
const arrow = tooltip
|
|
388
|
+
.append('div')
|
|
389
|
+
.attr(dataAttribute, 'true')
|
|
390
|
+
.attr('data-chart-tooltip-arrow-edge', connectorLayout.arrowEdge)
|
|
391
|
+
.attr('aria-hidden', 'true')
|
|
392
|
+
.style('position', 'absolute')
|
|
393
|
+
.style('left', `${position.left}px`)
|
|
394
|
+
.style('top', `${position.top}px`)
|
|
395
|
+
.style('width', '0')
|
|
396
|
+
.style('height', '0')
|
|
397
|
+
.style('pointer-events', 'none')
|
|
398
|
+
.style('z-index', String(zIndex));
|
|
399
|
+
if (connectorLayout.arrowEdge === 'left') {
|
|
400
|
+
arrow
|
|
401
|
+
.style('border-top', `${halfHeight}px solid transparent`)
|
|
402
|
+
.style('border-bottom', `${halfHeight}px solid transparent`)
|
|
403
|
+
.style('border-right', `${length}px solid ${color}`);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (connectorLayout.arrowEdge === 'right') {
|
|
407
|
+
arrow
|
|
408
|
+
.style('border-top', `${halfHeight}px solid transparent`)
|
|
409
|
+
.style('border-bottom', `${halfHeight}px solid transparent`)
|
|
410
|
+
.style('border-left', `${length}px solid ${color}`);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (connectorLayout.arrowEdge === 'top') {
|
|
414
|
+
arrow
|
|
415
|
+
.style('border-left', `${halfHeight}px solid transparent`)
|
|
416
|
+
.style('border-right', `${halfHeight}px solid transparent`)
|
|
417
|
+
.style('border-bottom', `${length}px solid ${color}`);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
arrow
|
|
421
|
+
.style('border-left', `${halfHeight}px solid transparent`)
|
|
422
|
+
.style('border-right', `${halfHeight}px solid transparent`)
|
|
423
|
+
.style('border-top', `${length}px solid ${color}`);
|
|
424
|
+
}
|
|
425
|
+
removeSplitTooltips() {
|
|
426
|
+
document
|
|
427
|
+
.querySelectorAll(`[data-chart-tooltip-owner="${this.splitTooltipOwner}"]`)
|
|
428
|
+
.forEach((node) => {
|
|
429
|
+
node.remove();
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
removeRootTooltip() {
|
|
433
|
+
const tooltip = this.tooltipDiv ?? select(`#${this.id}`);
|
|
434
|
+
if (!tooltip.empty()) {
|
|
435
|
+
tooltip.remove();
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TooltipBarAnchorPosition, TooltipPosition } from '../types.js';
|
|
2
|
+
import { type SplitTooltipLayout, type SplitTooltipViewportBounds, type TooltipAnchor, type TooltipArrowEdge, type TooltipConnectorLayout, type TooltipTarget, type XYTooltipSeries } from './types.js';
|
|
3
|
+
export declare function getSplitTooltipViewportBounds(): SplitTooltipViewportBounds;
|
|
4
|
+
export declare function resolveTooltipArrowEdge(position: TooltipPosition, anchor: TooltipAnchor, target: TooltipTarget, tooltipWidth: number, tooltipHeight: number): TooltipArrowEdge;
|
|
5
|
+
export declare function resolveSidePlacementArrowEdge(anchor: TooltipAnchor, tooltipWidth: number, bounds?: SplitTooltipViewportBounds): TooltipArrowEdge;
|
|
6
|
+
export declare function resolveVerticalPlacementArrowEdge(target: TooltipTarget, tooltipHeight: number, bounds?: SplitTooltipViewportBounds): TooltipArrowEdge;
|
|
7
|
+
export declare function resolveSharedTooltipTarget(anchor: TooltipAnchor): TooltipTarget;
|
|
8
|
+
export declare function resolveSplitTooltipTarget(currentSeries: XYTooltipSeries, anchor: TooltipAnchor, barAnchorPosition: TooltipBarAnchorPosition): TooltipTarget;
|
|
9
|
+
export declare function getAnchoredTooltipPosition(anchor: TooltipAnchor, target: TooltipTarget, tooltipWidth: number, tooltipHeight: number, arrowEdge: TooltipArrowEdge): {
|
|
10
|
+
left: number;
|
|
11
|
+
top: number;
|
|
12
|
+
} | null;
|
|
13
|
+
export declare function resolveTooltipConnectorLayout(arrowEdge: TooltipArrowEdge, tooltipLeft: number, tooltipTop: number, tooltipWidth: number, tooltipHeight: number, targetX: number, targetY: number, anchor: TooltipAnchor): TooltipConnectorLayout | null;
|
|
14
|
+
export declare function resolveTooltipArrowPosition(arrowEdge: TooltipArrowEdge, boxX: number, boxY: number, length: number, halfHeight: number): {
|
|
15
|
+
left: number;
|
|
16
|
+
top: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function resolveSplitTooltipPositions(layouts: SplitTooltipLayout[], position: TooltipPosition): void;
|