@dschz/solid-uplot 0.1.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/LICENSE +21 -0
- package/README.md +771 -0
- package/dist/chunk/3TJS44N7.js +15 -0
- package/dist/chunk/A3AZKFSW.jsx +27 -0
- package/dist/chunk/ZISGD6FJ.js +25 -0
- package/dist/createPluginBus-B_Gp5BCB.d.ts +21 -0
- package/dist/getCursorData-2qxURGuZ.d.ts +44 -0
- package/dist/getSeriesData-D1zBqQ9Y.d.ts +37 -0
- package/dist/index/index.d.ts +44 -0
- package/dist/index/index.js +117 -0
- package/dist/index/index.jsx +135 -0
- package/dist/plugins/index.d.ts +443 -0
- package/dist/plugins/index.js +342 -0
- package/dist/plugins/index.jsx +347 -0
- package/dist/utils/index.d.ts +24 -0
- package/dist/utils/index.js +26 -0
- package/package.json +116 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { getCursorData } from '../chunk/3TJS44N7.js';
|
|
2
|
+
import { getSeriesData } from '../chunk/ZISGD6FJ.js';
|
|
3
|
+
import { createRoot, createEffect, mergeProps, splitProps, Show } from 'solid-js';
|
|
4
|
+
import { render, createComponent, template, use, insert, effect, setAttribute, className, style } from 'solid-js/web';
|
|
5
|
+
|
|
6
|
+
// src/plugins/cursor.ts
|
|
7
|
+
var cursor = () => {
|
|
8
|
+
return ({ bus }) => {
|
|
9
|
+
if (!bus) {
|
|
10
|
+
return { hooks: {} };
|
|
11
|
+
}
|
|
12
|
+
bus.setData("cursor", {
|
|
13
|
+
state: {}
|
|
14
|
+
});
|
|
15
|
+
let pointerEnter;
|
|
16
|
+
let pointerLeave;
|
|
17
|
+
return {
|
|
18
|
+
hooks: {
|
|
19
|
+
ready: (u) => {
|
|
20
|
+
pointerEnter = () => {
|
|
21
|
+
bus.setData("cursor", { sourceId: u.root.id });
|
|
22
|
+
};
|
|
23
|
+
pointerLeave = () => {
|
|
24
|
+
bus.setData("cursor", { sourceId: void 0 });
|
|
25
|
+
};
|
|
26
|
+
u.over.addEventListener("pointerenter", pointerEnter);
|
|
27
|
+
u.over.addEventListener("pointerleave", pointerLeave);
|
|
28
|
+
},
|
|
29
|
+
setCursor: (u) => {
|
|
30
|
+
bus.setData("cursor", "state", u.root.id, getCursorData(u));
|
|
31
|
+
},
|
|
32
|
+
setData: (u) => {
|
|
33
|
+
bus.setData("cursor", (prev) => ({
|
|
34
|
+
...prev ?? {},
|
|
35
|
+
state: {
|
|
36
|
+
...prev?.state ?? {},
|
|
37
|
+
[u.root.id]: getCursorData(u)
|
|
38
|
+
}
|
|
39
|
+
}));
|
|
40
|
+
},
|
|
41
|
+
destroy: (u) => {
|
|
42
|
+
bus.setData("cursor", "state", u.root.id, void 0);
|
|
43
|
+
u.over.removeEventListener("pointerenter", pointerEnter);
|
|
44
|
+
u.over.removeEventListener("pointerleave", pointerLeave);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
var DEFAULT_UNFOCUSED_ALPHA = 0.1;
|
|
51
|
+
var DEFAULT_FOCUSED_ALPHA = 1;
|
|
52
|
+
var DEFAULT_REBUILD_PATHS = false;
|
|
53
|
+
var seriesFocusRedraw = (u, options = {}) => {
|
|
54
|
+
const {
|
|
55
|
+
unfocusedAlpha = DEFAULT_UNFOCUSED_ALPHA,
|
|
56
|
+
focusedAlpha = DEFAULT_FOCUSED_ALPHA,
|
|
57
|
+
rebuildPaths = DEFAULT_REBUILD_PATHS,
|
|
58
|
+
focusTargets
|
|
59
|
+
} = options;
|
|
60
|
+
for (let i = 1; i < u.series.length; i++) {
|
|
61
|
+
const s = u.series[i];
|
|
62
|
+
if (!focusTargets || !focusTargets.length) {
|
|
63
|
+
s.alpha = 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const target = focusTargets.find((t) => {
|
|
67
|
+
if ("label" in t) return t.label === s.label;
|
|
68
|
+
if ("zeroIndex" in t) return t.zeroIndex === i - 1;
|
|
69
|
+
if ("index" in t) return t.index === i;
|
|
70
|
+
});
|
|
71
|
+
s.alpha = target ? focusedAlpha : unfocusedAlpha;
|
|
72
|
+
}
|
|
73
|
+
u.redraw(rebuildPaths);
|
|
74
|
+
};
|
|
75
|
+
var focusSeries = (options = {}) => {
|
|
76
|
+
return ({ bus }) => {
|
|
77
|
+
if (!bus) {
|
|
78
|
+
return { hooks: {} };
|
|
79
|
+
}
|
|
80
|
+
const {
|
|
81
|
+
pxThreshold = 5,
|
|
82
|
+
unfocusedAlpha = DEFAULT_UNFOCUSED_ALPHA,
|
|
83
|
+
focusedAlpha = DEFAULT_FOCUSED_ALPHA,
|
|
84
|
+
rebuildPaths = DEFAULT_REBUILD_PATHS
|
|
85
|
+
} = options;
|
|
86
|
+
let dispose;
|
|
87
|
+
let pointerLeave;
|
|
88
|
+
return {
|
|
89
|
+
hooks: {
|
|
90
|
+
ready: (u) => {
|
|
91
|
+
pointerLeave = () => {
|
|
92
|
+
bus.setData("focusSeries", void 0);
|
|
93
|
+
};
|
|
94
|
+
queueMicrotask(() => {
|
|
95
|
+
if (bus.data.focusSeries) {
|
|
96
|
+
seriesFocusRedraw(u, {
|
|
97
|
+
unfocusedAlpha,
|
|
98
|
+
focusedAlpha,
|
|
99
|
+
rebuildPaths,
|
|
100
|
+
focusTargets: bus.data.focusSeries.targets
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
u.over.addEventListener("pointerleave", pointerLeave);
|
|
105
|
+
dispose = createRoot((dispose2) => {
|
|
106
|
+
createEffect(() => {
|
|
107
|
+
const cursor2 = bus.data.cursor;
|
|
108
|
+
const focus = bus.data.focusSeries;
|
|
109
|
+
if (cursor2?.sourceId !== u.root.id) {
|
|
110
|
+
seriesFocusRedraw(u, {
|
|
111
|
+
unfocusedAlpha,
|
|
112
|
+
focusedAlpha,
|
|
113
|
+
rebuildPaths,
|
|
114
|
+
focusTargets: focus?.targets
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return dispose2;
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
setCursor: (u) => {
|
|
122
|
+
const cursor2 = bus.data.cursor;
|
|
123
|
+
const chartCursor = cursor2?.state[u.root.id];
|
|
124
|
+
if (!cursor2 || !chartCursor || cursor2.sourceId !== u.root.id) return;
|
|
125
|
+
const focusTargets = [];
|
|
126
|
+
for (let i = 1; i < u.series.length; i++) {
|
|
127
|
+
const s = u.series[i];
|
|
128
|
+
const yVals = u.data[i];
|
|
129
|
+
const val = yVals?.[chartCursor.idx];
|
|
130
|
+
if (!s.show || !yVals || val == null) continue;
|
|
131
|
+
const yPos = u.valToPos(val, s.scale);
|
|
132
|
+
const dist = Math.abs(yPos - chartCursor.position.top);
|
|
133
|
+
if (dist <= pxThreshold) {
|
|
134
|
+
if (s.label != null) {
|
|
135
|
+
focusTargets.push({ label: s.label });
|
|
136
|
+
} else {
|
|
137
|
+
focusTargets.push({ index: i });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
seriesFocusRedraw(u, {
|
|
142
|
+
unfocusedAlpha,
|
|
143
|
+
focusedAlpha,
|
|
144
|
+
rebuildPaths,
|
|
145
|
+
focusTargets
|
|
146
|
+
});
|
|
147
|
+
bus.setData("focusSeries", {
|
|
148
|
+
sourceId: u.root.id,
|
|
149
|
+
targets: focusTargets
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
destroy: (u) => {
|
|
153
|
+
dispose();
|
|
154
|
+
u.over.removeEventListener("pointerleave", pointerLeave);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
var _tmpl$ = /* @__PURE__ */ template(`<div role=group aria-label="Chart legend">`);
|
|
161
|
+
var legend = (Component, options = {}) => {
|
|
162
|
+
return ({
|
|
163
|
+
bus
|
|
164
|
+
}) => {
|
|
165
|
+
if (!bus) {
|
|
166
|
+
return {
|
|
167
|
+
hooks: {}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
let legendRoot;
|
|
171
|
+
let dispose;
|
|
172
|
+
return {
|
|
173
|
+
hooks: {
|
|
174
|
+
ready: (u) => {
|
|
175
|
+
const seriesData = getSeriesData(u);
|
|
176
|
+
const LegendRoot = () => {
|
|
177
|
+
const _options = mergeProps({
|
|
178
|
+
placement: "top-left",
|
|
179
|
+
pxOffset: 8,
|
|
180
|
+
id: "solid-uplot-legend-root",
|
|
181
|
+
zIndex: 10
|
|
182
|
+
}, options);
|
|
183
|
+
const [legendOptions, containerProps] = splitProps(_options, ["placement", "pxOffset"]);
|
|
184
|
+
const containerStyle = () => {
|
|
185
|
+
const overRect = u.over.getBoundingClientRect();
|
|
186
|
+
const offset = legendOptions.pxOffset;
|
|
187
|
+
return {
|
|
188
|
+
position: "absolute",
|
|
189
|
+
[legendOptions.placement === "top-left" ? "left" : "right"]: `${offset}px`,
|
|
190
|
+
top: `${offset}px`,
|
|
191
|
+
"max-width": `${overRect.width - offset * 2}px`,
|
|
192
|
+
"max-height": `${overRect.height - offset * 2}px`,
|
|
193
|
+
"z-index": containerProps.zIndex,
|
|
194
|
+
"pointer-events": "auto",
|
|
195
|
+
overflow: "auto",
|
|
196
|
+
...containerProps.style
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
return (() => {
|
|
200
|
+
var _el$ = _tmpl$();
|
|
201
|
+
var _ref$ = legendRoot;
|
|
202
|
+
typeof _ref$ === "function" ? use(_ref$, _el$) : legendRoot = _el$;
|
|
203
|
+
insert(_el$, createComponent(Component, {
|
|
204
|
+
u,
|
|
205
|
+
seriesData,
|
|
206
|
+
bus
|
|
207
|
+
}));
|
|
208
|
+
effect((_p$) => {
|
|
209
|
+
var _v$ = containerProps.id, _v$2 = containerProps.class, _v$3 = containerStyle();
|
|
210
|
+
_v$ !== _p$.e && setAttribute(_el$, "id", _p$.e = _v$);
|
|
211
|
+
_v$2 !== _p$.t && className(_el$, _p$.t = _v$2);
|
|
212
|
+
_p$.a = style(_el$, _v$3, _p$.a);
|
|
213
|
+
return _p$;
|
|
214
|
+
}, {
|
|
215
|
+
e: void 0,
|
|
216
|
+
t: void 0,
|
|
217
|
+
a: void 0
|
|
218
|
+
});
|
|
219
|
+
return _el$;
|
|
220
|
+
})();
|
|
221
|
+
};
|
|
222
|
+
dispose = render(() => createComponent(LegendRoot, {}), u.over);
|
|
223
|
+
},
|
|
224
|
+
destroy: () => {
|
|
225
|
+
dispose();
|
|
226
|
+
legendRoot?.remove();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
var _tmpl$2 = /* @__PURE__ */ template(`<div role=tooltip aria-label="Chart tooltip">`);
|
|
233
|
+
var TOOLTIP_OFFSET_X = 8;
|
|
234
|
+
var TOOLTIP_OFFSET_Y = 8;
|
|
235
|
+
var getTooltipPosition = (placement, left, top, tooltipWidth, tooltipHeight) => {
|
|
236
|
+
const baseX = placement.includes("left") ? left - tooltipWidth - TOOLTIP_OFFSET_X : left + TOOLTIP_OFFSET_X;
|
|
237
|
+
const baseY = placement.includes("top") ? top - tooltipHeight - TOOLTIP_OFFSET_Y : top + TOOLTIP_OFFSET_Y;
|
|
238
|
+
const viewportX = baseX - window.scrollX;
|
|
239
|
+
const viewportY = baseY - window.scrollY;
|
|
240
|
+
const overflowsLeft = viewportX < 0;
|
|
241
|
+
const overflowsRight = viewportX + tooltipWidth > window.innerWidth;
|
|
242
|
+
const overflowsTop = viewportY < 0;
|
|
243
|
+
const overflowsBottom = viewportY + tooltipHeight > window.innerHeight;
|
|
244
|
+
let flipX = false;
|
|
245
|
+
let flipY = false;
|
|
246
|
+
if (placement.includes("left") && overflowsLeft) flipX = true;
|
|
247
|
+
if (placement.includes("right") && overflowsRight) flipX = true;
|
|
248
|
+
if (placement.includes("top") && overflowsTop) flipY = true;
|
|
249
|
+
if (placement.includes("bottom") && overflowsBottom) flipY = true;
|
|
250
|
+
const finalX = flipX && placement.includes("left") ? left + TOOLTIP_OFFSET_X : flipX && placement.includes("right") ? left - tooltipWidth - TOOLTIP_OFFSET_X : baseX;
|
|
251
|
+
const finalY = flipY && placement.includes("top") ? top + TOOLTIP_OFFSET_Y : flipY && placement.includes("bottom") ? top - tooltipHeight - TOOLTIP_OFFSET_Y : baseY;
|
|
252
|
+
return {
|
|
253
|
+
left: finalX,
|
|
254
|
+
top: finalY
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
var tooltip = (Component, options = {}) => {
|
|
258
|
+
return ({
|
|
259
|
+
bus
|
|
260
|
+
}) => {
|
|
261
|
+
if (!bus) {
|
|
262
|
+
return {
|
|
263
|
+
hooks: {}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
let tooltipRoot;
|
|
267
|
+
let dispose;
|
|
268
|
+
return {
|
|
269
|
+
hooks: {
|
|
270
|
+
ready: (u) => {
|
|
271
|
+
const seriesData = getSeriesData(u);
|
|
272
|
+
const TooltipRoot = () => {
|
|
273
|
+
const _options = mergeProps({
|
|
274
|
+
placement: "top-left",
|
|
275
|
+
id: "solid-uplot-tooltip-root",
|
|
276
|
+
style: {},
|
|
277
|
+
zIndex: 20
|
|
278
|
+
}, options);
|
|
279
|
+
const chartCursorData = () => bus.data.cursor?.state[u.root.id];
|
|
280
|
+
const [tooltipOptions, containerProps] = splitProps(_options, ["placement"]);
|
|
281
|
+
return createComponent(Show, {
|
|
282
|
+
get when() {
|
|
283
|
+
return chartCursorData();
|
|
284
|
+
},
|
|
285
|
+
children: (cursor2) => {
|
|
286
|
+
const position = () => {
|
|
287
|
+
const overRect = u.over.getBoundingClientRect();
|
|
288
|
+
const absoluteLeft = overRect.left + cursor2().position.left + window.scrollX;
|
|
289
|
+
const absoluteTop = overRect.top + cursor2().position.top + window.scrollY;
|
|
290
|
+
const tooltipWidth = tooltipRoot.offsetWidth ?? 0;
|
|
291
|
+
const tooltipHeight = tooltipRoot.offsetHeight ?? 0;
|
|
292
|
+
return getTooltipPosition(tooltipOptions.placement, absoluteLeft, absoluteTop, tooltipWidth, tooltipHeight);
|
|
293
|
+
};
|
|
294
|
+
return (() => {
|
|
295
|
+
var _el$ = _tmpl$2();
|
|
296
|
+
var _ref$ = tooltipRoot;
|
|
297
|
+
typeof _ref$ === "function" ? use(_ref$, _el$) : tooltipRoot = _el$;
|
|
298
|
+
insert(_el$, createComponent(Component, {
|
|
299
|
+
u,
|
|
300
|
+
seriesData,
|
|
301
|
+
get cursor() {
|
|
302
|
+
return cursor2();
|
|
303
|
+
},
|
|
304
|
+
get focusedSeries() {
|
|
305
|
+
return bus.data.focusSeries;
|
|
306
|
+
}
|
|
307
|
+
}));
|
|
308
|
+
effect((_p$) => {
|
|
309
|
+
var _v$ = containerProps.id, _v$2 = containerProps.class, _v$3 = {
|
|
310
|
+
position: "absolute",
|
|
311
|
+
"z-index": containerProps.zIndex,
|
|
312
|
+
left: `${position().left}px`,
|
|
313
|
+
top: `${position().top}px`,
|
|
314
|
+
"pointer-events": "none",
|
|
315
|
+
...containerProps.style
|
|
316
|
+
};
|
|
317
|
+
_v$ !== _p$.e && setAttribute(_el$, "id", _p$.e = _v$);
|
|
318
|
+
_v$2 !== _p$.t && className(_el$, _p$.t = _v$2);
|
|
319
|
+
_p$.a = style(_el$, _v$3, _p$.a);
|
|
320
|
+
return _p$;
|
|
321
|
+
}, {
|
|
322
|
+
e: void 0,
|
|
323
|
+
t: void 0,
|
|
324
|
+
a: void 0
|
|
325
|
+
});
|
|
326
|
+
return _el$;
|
|
327
|
+
})();
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
};
|
|
331
|
+
dispose = render(() => createComponent(TooltipRoot, {}), u.root);
|
|
332
|
+
},
|
|
333
|
+
destroy: () => {
|
|
334
|
+
dispose();
|
|
335
|
+
tooltipRoot?.remove();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export { cursor, focusSeries, legend, tooltip };
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSeriesData
|
|
3
|
+
} from "../chunk/A3AZKFSW.jsx";
|
|
4
|
+
|
|
5
|
+
// src/utils/getCursorData.ts
|
|
6
|
+
var getCursorData = (u) => {
|
|
7
|
+
const idx = u.cursor.idx;
|
|
8
|
+
const xValues = u.data[0];
|
|
9
|
+
const isValid = idx != null && xValues && idx < xValues.length;
|
|
10
|
+
return !isValid ? void 0 : {
|
|
11
|
+
plotId: u.root.id,
|
|
12
|
+
idx,
|
|
13
|
+
xValue: xValues[idx],
|
|
14
|
+
visible: Boolean(u.cursor.show),
|
|
15
|
+
position: { left: u.cursor.left || 0, top: u.cursor.top || 0 }
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/plugins/cursor.ts
|
|
20
|
+
var cursor = () => {
|
|
21
|
+
return ({ bus }) => {
|
|
22
|
+
if (!bus) {
|
|
23
|
+
return { hooks: {} };
|
|
24
|
+
}
|
|
25
|
+
bus.setData("cursor", {
|
|
26
|
+
state: {}
|
|
27
|
+
});
|
|
28
|
+
let pointerEnter;
|
|
29
|
+
let pointerLeave;
|
|
30
|
+
return {
|
|
31
|
+
hooks: {
|
|
32
|
+
ready: (u) => {
|
|
33
|
+
pointerEnter = () => {
|
|
34
|
+
bus.setData("cursor", { sourceId: u.root.id });
|
|
35
|
+
};
|
|
36
|
+
pointerLeave = () => {
|
|
37
|
+
bus.setData("cursor", { sourceId: void 0 });
|
|
38
|
+
};
|
|
39
|
+
u.over.addEventListener("pointerenter", pointerEnter);
|
|
40
|
+
u.over.addEventListener("pointerleave", pointerLeave);
|
|
41
|
+
},
|
|
42
|
+
setCursor: (u) => {
|
|
43
|
+
bus.setData("cursor", "state", u.root.id, getCursorData(u));
|
|
44
|
+
},
|
|
45
|
+
setData: (u) => {
|
|
46
|
+
bus.setData("cursor", (prev) => ({
|
|
47
|
+
...prev ?? {},
|
|
48
|
+
state: {
|
|
49
|
+
...prev?.state ?? {},
|
|
50
|
+
[u.root.id]: getCursorData(u)
|
|
51
|
+
}
|
|
52
|
+
}));
|
|
53
|
+
},
|
|
54
|
+
destroy: (u) => {
|
|
55
|
+
bus.setData("cursor", "state", u.root.id, void 0);
|
|
56
|
+
u.over.removeEventListener("pointerenter", pointerEnter);
|
|
57
|
+
u.over.removeEventListener("pointerleave", pointerLeave);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/plugins/focusSeries.ts
|
|
65
|
+
import { createEffect, createRoot } from "solid-js";
|
|
66
|
+
var DEFAULT_UNFOCUSED_ALPHA = 0.1;
|
|
67
|
+
var DEFAULT_FOCUSED_ALPHA = 1;
|
|
68
|
+
var DEFAULT_REBUILD_PATHS = false;
|
|
69
|
+
var seriesFocusRedraw = (u, options = {}) => {
|
|
70
|
+
const {
|
|
71
|
+
unfocusedAlpha = DEFAULT_UNFOCUSED_ALPHA,
|
|
72
|
+
focusedAlpha = DEFAULT_FOCUSED_ALPHA,
|
|
73
|
+
rebuildPaths = DEFAULT_REBUILD_PATHS,
|
|
74
|
+
focusTargets
|
|
75
|
+
} = options;
|
|
76
|
+
for (let i = 1; i < u.series.length; i++) {
|
|
77
|
+
const s = u.series[i];
|
|
78
|
+
if (!focusTargets || !focusTargets.length) {
|
|
79
|
+
s.alpha = 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const target = focusTargets.find((t) => {
|
|
83
|
+
if ("label" in t) return t.label === s.label;
|
|
84
|
+
if ("zeroIndex" in t) return t.zeroIndex === i - 1;
|
|
85
|
+
if ("index" in t) return t.index === i;
|
|
86
|
+
});
|
|
87
|
+
s.alpha = target ? focusedAlpha : unfocusedAlpha;
|
|
88
|
+
}
|
|
89
|
+
u.redraw(rebuildPaths);
|
|
90
|
+
};
|
|
91
|
+
var focusSeries = (options = {}) => {
|
|
92
|
+
return ({ bus }) => {
|
|
93
|
+
if (!bus) {
|
|
94
|
+
return { hooks: {} };
|
|
95
|
+
}
|
|
96
|
+
const {
|
|
97
|
+
pxThreshold = 5,
|
|
98
|
+
unfocusedAlpha = DEFAULT_UNFOCUSED_ALPHA,
|
|
99
|
+
focusedAlpha = DEFAULT_FOCUSED_ALPHA,
|
|
100
|
+
rebuildPaths = DEFAULT_REBUILD_PATHS
|
|
101
|
+
} = options;
|
|
102
|
+
let dispose;
|
|
103
|
+
let pointerLeave;
|
|
104
|
+
return {
|
|
105
|
+
hooks: {
|
|
106
|
+
ready: (u) => {
|
|
107
|
+
pointerLeave = () => {
|
|
108
|
+
bus.setData("focusSeries", void 0);
|
|
109
|
+
};
|
|
110
|
+
queueMicrotask(() => {
|
|
111
|
+
if (bus.data.focusSeries) {
|
|
112
|
+
seriesFocusRedraw(u, {
|
|
113
|
+
unfocusedAlpha,
|
|
114
|
+
focusedAlpha,
|
|
115
|
+
rebuildPaths,
|
|
116
|
+
focusTargets: bus.data.focusSeries.targets
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
u.over.addEventListener("pointerleave", pointerLeave);
|
|
121
|
+
dispose = createRoot((dispose2) => {
|
|
122
|
+
createEffect(() => {
|
|
123
|
+
const cursor2 = bus.data.cursor;
|
|
124
|
+
const focus = bus.data.focusSeries;
|
|
125
|
+
if (cursor2?.sourceId !== u.root.id) {
|
|
126
|
+
seriesFocusRedraw(u, {
|
|
127
|
+
unfocusedAlpha,
|
|
128
|
+
focusedAlpha,
|
|
129
|
+
rebuildPaths,
|
|
130
|
+
focusTargets: focus?.targets
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return dispose2;
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
setCursor: (u) => {
|
|
138
|
+
const cursor2 = bus.data.cursor;
|
|
139
|
+
const chartCursor = cursor2?.state[u.root.id];
|
|
140
|
+
if (!cursor2 || !chartCursor || cursor2.sourceId !== u.root.id) return;
|
|
141
|
+
const focusTargets = [];
|
|
142
|
+
for (let i = 1; i < u.series.length; i++) {
|
|
143
|
+
const s = u.series[i];
|
|
144
|
+
const yVals = u.data[i];
|
|
145
|
+
const val = yVals?.[chartCursor.idx];
|
|
146
|
+
if (!s.show || !yVals || val == null) continue;
|
|
147
|
+
const yPos = u.valToPos(val, s.scale);
|
|
148
|
+
const dist = Math.abs(yPos - chartCursor.position.top);
|
|
149
|
+
if (dist <= pxThreshold) {
|
|
150
|
+
if (s.label != null) {
|
|
151
|
+
focusTargets.push({ label: s.label });
|
|
152
|
+
} else {
|
|
153
|
+
focusTargets.push({ index: i });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
seriesFocusRedraw(u, {
|
|
158
|
+
unfocusedAlpha,
|
|
159
|
+
focusedAlpha,
|
|
160
|
+
rebuildPaths,
|
|
161
|
+
focusTargets
|
|
162
|
+
});
|
|
163
|
+
bus.setData("focusSeries", {
|
|
164
|
+
sourceId: u.root.id,
|
|
165
|
+
targets: focusTargets
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
destroy: (u) => {
|
|
169
|
+
dispose();
|
|
170
|
+
u.over.removeEventListener("pointerleave", pointerLeave);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// src/plugins/legend.tsx
|
|
178
|
+
import { mergeProps, splitProps } from "solid-js";
|
|
179
|
+
import { render } from "solid-js/web";
|
|
180
|
+
import "uplot";
|
|
181
|
+
var legend = (Component, options = {}) => {
|
|
182
|
+
return ({ bus }) => {
|
|
183
|
+
if (!bus) {
|
|
184
|
+
return { hooks: {} };
|
|
185
|
+
}
|
|
186
|
+
let legendRoot;
|
|
187
|
+
let dispose;
|
|
188
|
+
return {
|
|
189
|
+
hooks: {
|
|
190
|
+
ready: (u) => {
|
|
191
|
+
const seriesData = getSeriesData(u);
|
|
192
|
+
const LegendRoot = () => {
|
|
193
|
+
const _options = mergeProps(
|
|
194
|
+
{
|
|
195
|
+
placement: "top-left",
|
|
196
|
+
pxOffset: 8,
|
|
197
|
+
id: "solid-uplot-legend-root",
|
|
198
|
+
zIndex: 10
|
|
199
|
+
},
|
|
200
|
+
options
|
|
201
|
+
);
|
|
202
|
+
const [legendOptions, containerProps] = splitProps(_options, ["placement", "pxOffset"]);
|
|
203
|
+
const containerStyle = () => {
|
|
204
|
+
const overRect = u.over.getBoundingClientRect();
|
|
205
|
+
const offset = legendOptions.pxOffset;
|
|
206
|
+
return {
|
|
207
|
+
position: "absolute",
|
|
208
|
+
[legendOptions.placement === "top-left" ? "left" : "right"]: `${offset}px`,
|
|
209
|
+
top: `${offset}px`,
|
|
210
|
+
"max-width": `${overRect.width - offset * 2}px`,
|
|
211
|
+
"max-height": `${overRect.height - offset * 2}px`,
|
|
212
|
+
"z-index": containerProps.zIndex,
|
|
213
|
+
"pointer-events": "auto",
|
|
214
|
+
overflow: "auto",
|
|
215
|
+
...containerProps.style
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
return <div
|
|
219
|
+
ref={legendRoot}
|
|
220
|
+
id={containerProps.id}
|
|
221
|
+
class={containerProps.class}
|
|
222
|
+
role="group"
|
|
223
|
+
aria-label="Chart legend"
|
|
224
|
+
style={containerStyle()}
|
|
225
|
+
>
|
|
226
|
+
<Component u={u} seriesData={seriesData} bus={bus} />
|
|
227
|
+
</div>;
|
|
228
|
+
};
|
|
229
|
+
dispose = render(() => <LegendRoot />, u.over);
|
|
230
|
+
},
|
|
231
|
+
destroy: () => {
|
|
232
|
+
dispose();
|
|
233
|
+
legendRoot?.remove();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// src/plugins/tooltip.tsx
|
|
241
|
+
import { mergeProps as mergeProps2, Show, splitProps as splitProps2 } from "solid-js";
|
|
242
|
+
import { render as render2 } from "solid-js/web";
|
|
243
|
+
import "uplot";
|
|
244
|
+
var TOOLTIP_OFFSET_X = 8;
|
|
245
|
+
var TOOLTIP_OFFSET_Y = 8;
|
|
246
|
+
var getTooltipPosition = (placement, left, top, tooltipWidth, tooltipHeight) => {
|
|
247
|
+
const baseX = placement.includes("left") ? left - tooltipWidth - TOOLTIP_OFFSET_X : left + TOOLTIP_OFFSET_X;
|
|
248
|
+
const baseY = placement.includes("top") ? top - tooltipHeight - TOOLTIP_OFFSET_Y : top + TOOLTIP_OFFSET_Y;
|
|
249
|
+
const viewportX = baseX - window.scrollX;
|
|
250
|
+
const viewportY = baseY - window.scrollY;
|
|
251
|
+
const overflowsLeft = viewportX < 0;
|
|
252
|
+
const overflowsRight = viewportX + tooltipWidth > window.innerWidth;
|
|
253
|
+
const overflowsTop = viewportY < 0;
|
|
254
|
+
const overflowsBottom = viewportY + tooltipHeight > window.innerHeight;
|
|
255
|
+
let flipX = false;
|
|
256
|
+
let flipY = false;
|
|
257
|
+
if (placement.includes("left") && overflowsLeft) flipX = true;
|
|
258
|
+
if (placement.includes("right") && overflowsRight) flipX = true;
|
|
259
|
+
if (placement.includes("top") && overflowsTop) flipY = true;
|
|
260
|
+
if (placement.includes("bottom") && overflowsBottom) flipY = true;
|
|
261
|
+
const finalX = flipX && placement.includes("left") ? left + TOOLTIP_OFFSET_X : flipX && placement.includes("right") ? left - tooltipWidth - TOOLTIP_OFFSET_X : baseX;
|
|
262
|
+
const finalY = flipY && placement.includes("top") ? top + TOOLTIP_OFFSET_Y : flipY && placement.includes("bottom") ? top - tooltipHeight - TOOLTIP_OFFSET_Y : baseY;
|
|
263
|
+
return {
|
|
264
|
+
left: finalX,
|
|
265
|
+
top: finalY
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
var tooltip = (Component, options = {}) => {
|
|
269
|
+
return ({ bus }) => {
|
|
270
|
+
if (!bus) {
|
|
271
|
+
return { hooks: {} };
|
|
272
|
+
}
|
|
273
|
+
let tooltipRoot;
|
|
274
|
+
let dispose;
|
|
275
|
+
return {
|
|
276
|
+
hooks: {
|
|
277
|
+
ready: (u) => {
|
|
278
|
+
const seriesData = getSeriesData(u);
|
|
279
|
+
const TooltipRoot = () => {
|
|
280
|
+
const _options = mergeProps2(
|
|
281
|
+
{
|
|
282
|
+
placement: "top-left",
|
|
283
|
+
id: "solid-uplot-tooltip-root",
|
|
284
|
+
style: {},
|
|
285
|
+
zIndex: 20
|
|
286
|
+
},
|
|
287
|
+
options
|
|
288
|
+
);
|
|
289
|
+
const chartCursorData = () => bus.data.cursor?.state[u.root.id];
|
|
290
|
+
const [tooltipOptions, containerProps] = splitProps2(_options, ["placement"]);
|
|
291
|
+
return <Show when={chartCursorData()}>
|
|
292
|
+
{(cursor2) => {
|
|
293
|
+
const position = () => {
|
|
294
|
+
const overRect = u.over.getBoundingClientRect();
|
|
295
|
+
const absoluteLeft = overRect.left + cursor2().position.left + window.scrollX;
|
|
296
|
+
const absoluteTop = overRect.top + cursor2().position.top + window.scrollY;
|
|
297
|
+
const tooltipWidth = tooltipRoot.offsetWidth ?? 0;
|
|
298
|
+
const tooltipHeight = tooltipRoot.offsetHeight ?? 0;
|
|
299
|
+
return getTooltipPosition(
|
|
300
|
+
tooltipOptions.placement,
|
|
301
|
+
absoluteLeft,
|
|
302
|
+
absoluteTop,
|
|
303
|
+
tooltipWidth,
|
|
304
|
+
tooltipHeight
|
|
305
|
+
);
|
|
306
|
+
};
|
|
307
|
+
return <div
|
|
308
|
+
ref={tooltipRoot}
|
|
309
|
+
id={containerProps.id}
|
|
310
|
+
class={containerProps.class}
|
|
311
|
+
role="tooltip"
|
|
312
|
+
aria-label="Chart tooltip"
|
|
313
|
+
style={{
|
|
314
|
+
position: "absolute",
|
|
315
|
+
"z-index": containerProps.zIndex,
|
|
316
|
+
left: `${position().left}px`,
|
|
317
|
+
top: `${position().top}px`,
|
|
318
|
+
"pointer-events": "none",
|
|
319
|
+
...containerProps.style
|
|
320
|
+
}}
|
|
321
|
+
>
|
|
322
|
+
<Component
|
|
323
|
+
u={u}
|
|
324
|
+
seriesData={seriesData}
|
|
325
|
+
cursor={cursor2()}
|
|
326
|
+
focusedSeries={bus.data.focusSeries}
|
|
327
|
+
/>
|
|
328
|
+
</div>;
|
|
329
|
+
}}
|
|
330
|
+
</Show>;
|
|
331
|
+
};
|
|
332
|
+
dispose = render2(() => <TooltipRoot />, u.root);
|
|
333
|
+
},
|
|
334
|
+
destroy: () => {
|
|
335
|
+
dispose();
|
|
336
|
+
tooltipRoot?.remove();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
};
|
|
342
|
+
export {
|
|
343
|
+
cursor,
|
|
344
|
+
focusSeries,
|
|
345
|
+
legend,
|
|
346
|
+
tooltip
|
|
347
|
+
};
|