UNPKG

@dschz/solid-uplot

Version:

SolidJS wrapper for uPlot — ultra-fast, tiny time-series & charting library

342 lines (339 loc) 11.9 kB
import { getCursorData, getSeriesData } from '../chunk/QELYARMN.js'; import { createRoot, createEffect, mergeProps, splitProps, Show } from 'solid-js'; import { render, createComponent, template, use, insert, effect, setAttribute, className, style } from 'solid-js/web'; // src/plugins/cursor.ts var cursor = () => { return ({ bus }) => { if (!bus) { return { hooks: {} }; } bus.setData("cursor", { state: {} }); let pointerEnter; let pointerLeave; return { hooks: { ready: (u) => { pointerEnter = () => { bus.setData("cursor", { sourceId: u.root.id }); }; pointerLeave = () => { bus.setData("cursor", { sourceId: void 0 }); }; u.over.addEventListener("pointerenter", pointerEnter); u.over.addEventListener("pointerleave", pointerLeave); }, setCursor: (u) => { bus.setData("cursor", "state", u.root.id, getCursorData(u)); }, setData: (u) => { bus.setData("cursor", (prev) => ({ ...prev ?? {}, state: { ...prev?.state ?? {}, [u.root.id]: getCursorData(u) } })); }, destroy: (u) => { bus.setData("cursor", "state", u.root.id, void 0); u.over.removeEventListener("pointerenter", pointerEnter); u.over.removeEventListener("pointerleave", pointerLeave); } } }; }; }; var DEFAULT_UNFOCUSED_ALPHA = 0.1; var DEFAULT_FOCUSED_ALPHA = 1; var DEFAULT_REBUILD_PATHS = false; var seriesFocusRedraw = (u, options = {}) => { const { unfocusedAlpha = DEFAULT_UNFOCUSED_ALPHA, focusedAlpha = DEFAULT_FOCUSED_ALPHA, rebuildPaths = DEFAULT_REBUILD_PATHS, focusTargets } = options; for (let i = 1; i < u.series.length; i++) { const s = u.series[i]; if (!focusTargets || !focusTargets.length) { s.alpha = 1; continue; } const target = focusTargets.find((t) => { if ("label" in t) return t.label === s.label; if ("zeroIndex" in t) return t.zeroIndex === i - 1; if ("index" in t) return t.index === i; }); s.alpha = target ? focusedAlpha : unfocusedAlpha; } u.redraw(rebuildPaths); }; var focusSeries = (options = {}) => { return ({ bus }) => { if (!bus) { return { hooks: {} }; } const { pxThreshold = 5, unfocusedAlpha = DEFAULT_UNFOCUSED_ALPHA, focusedAlpha = DEFAULT_FOCUSED_ALPHA, rebuildPaths = DEFAULT_REBUILD_PATHS } = options; let dispose; let pointerLeave; return { hooks: { ready: (u) => { pointerLeave = () => { bus.setData("focusSeries", void 0); }; queueMicrotask(() => { if (bus.data.focusSeries) { seriesFocusRedraw(u, { unfocusedAlpha, focusedAlpha, rebuildPaths, focusTargets: bus.data.focusSeries.targets }); } }); u.over.addEventListener("pointerleave", pointerLeave); dispose = createRoot((dispose2) => { createEffect(() => { const cursor2 = bus.data.cursor; const focus = bus.data.focusSeries; if (cursor2?.sourceId !== u.root.id) { seriesFocusRedraw(u, { unfocusedAlpha, focusedAlpha, rebuildPaths, focusTargets: focus?.targets }); } }); return dispose2; }); }, setCursor: (u) => { const cursor2 = bus.data.cursor; const chartCursor = cursor2?.state[u.root.id]; if (!cursor2 || !chartCursor || cursor2.sourceId !== u.root.id) return; const focusTargets = []; for (let i = 1; i < u.series.length; i++) { const s = u.series[i]; const yVals = u.data[i]; const val = yVals?.[chartCursor.idx]; if (!s.show || !yVals || val == null) continue; const yPos = u.valToPos(val, s.scale); const dist = Math.abs(yPos - chartCursor.position.top); if (dist <= pxThreshold) { if (s.label != null) { focusTargets.push({ label: s.label }); } else { focusTargets.push({ index: i }); } } } seriesFocusRedraw(u, { unfocusedAlpha, focusedAlpha, rebuildPaths, focusTargets }); bus.setData("focusSeries", { sourceId: u.root.id, targets: focusTargets }); }, destroy: (u) => { dispose(); u.over.removeEventListener("pointerleave", pointerLeave); } } }; }; }; var _tmpl$ = /* @__PURE__ */ template(`<div role=group aria-label="Chart legend">`); var legend = (Component, options = {}) => { return ({ bus }) => { if (!bus) { return { hooks: {} }; } let legendRoot; let dispose; return { hooks: { ready: (u) => { const seriesData = getSeriesData(u); const LegendRoot = () => { const _options = mergeProps({ placement: "top-left", pxOffset: 8, id: "solid-uplot-legend-root", zIndex: 10 }, options); const [legendOptions, containerProps] = splitProps(_options, ["placement", "pxOffset"]); const containerStyle = () => { const overRect = u.over.getBoundingClientRect(); const offset = legendOptions.pxOffset; return { position: "absolute", [legendOptions.placement === "top-left" ? "left" : "right"]: `${offset}px`, top: `${offset}px`, "max-width": `${overRect.width - offset * 2}px`, "max-height": `${overRect.height - offset * 2}px`, "z-index": containerProps.zIndex, "pointer-events": "auto", overflow: "auto", ...containerProps.style }; }; return (() => { var _el$ = _tmpl$(); var _ref$ = legendRoot; typeof _ref$ === "function" ? use(_ref$, _el$) : legendRoot = _el$; insert(_el$, createComponent(Component, { u, seriesData, bus })); effect((_p$) => { var _v$ = containerProps.id, _v$2 = containerProps.class, _v$3 = containerStyle(); _v$ !== _p$.e && setAttribute(_el$, "id", _p$.e = _v$); _v$2 !== _p$.t && className(_el$, _p$.t = _v$2); _p$.a = style(_el$, _v$3, _p$.a); return _p$; }, { e: void 0, t: void 0, a: void 0 }); return _el$; })(); }; dispose = render(() => createComponent(LegendRoot, {}), u.over); }, destroy: () => { dispose(); legendRoot?.remove(); } } }; }; }; var _tmpl$2 = /* @__PURE__ */ template(`<div role=tooltip aria-label="Chart tooltip">`); var TOOLTIP_OFFSET_X = 8; var TOOLTIP_OFFSET_Y = 8; var getTooltipPosition = (placement, left, top, tooltipWidth, tooltipHeight) => { const baseX = placement.includes("left") ? left - tooltipWidth - TOOLTIP_OFFSET_X : left + TOOLTIP_OFFSET_X; const baseY = placement.includes("top") ? top - tooltipHeight - TOOLTIP_OFFSET_Y : top + TOOLTIP_OFFSET_Y; const viewportX = baseX - window.scrollX; const viewportY = baseY - window.scrollY; const overflowsLeft = viewportX < 0; const overflowsRight = viewportX + tooltipWidth > window.innerWidth; const overflowsTop = viewportY < 0; const overflowsBottom = viewportY + tooltipHeight > window.innerHeight; let flipX = false; let flipY = false; if (placement.includes("left") && overflowsLeft) flipX = true; if (placement.includes("right") && overflowsRight) flipX = true; if (placement.includes("top") && overflowsTop) flipY = true; if (placement.includes("bottom") && overflowsBottom) flipY = true; const finalX = flipX && placement.includes("left") ? left + TOOLTIP_OFFSET_X : flipX && placement.includes("right") ? left - tooltipWidth - TOOLTIP_OFFSET_X : baseX; const finalY = flipY && placement.includes("top") ? top + TOOLTIP_OFFSET_Y : flipY && placement.includes("bottom") ? top - tooltipHeight - TOOLTIP_OFFSET_Y : baseY; return { left: finalX, top: finalY }; }; var tooltip = (Component, options = {}) => { return ({ bus }) => { if (!bus) { return { hooks: {} }; } let tooltipRoot; let dispose; return { hooks: { ready: (u) => { const seriesData = getSeriesData(u); const TooltipRoot = () => { const _options = mergeProps({ placement: "top-left", id: "solid-uplot-tooltip-root", style: {}, zIndex: 20 }, options); const chartCursorData = () => bus.data.cursor?.state[u.root.id]; const [tooltipOptions, containerProps] = splitProps(_options, ["placement"]); return createComponent(Show, { get when() { return chartCursorData(); }, children: (cursor2) => { const position = () => { const overRect = u.over.getBoundingClientRect(); const absoluteLeft = overRect.left + cursor2().position.left + window.scrollX; const absoluteTop = overRect.top + cursor2().position.top + window.scrollY; const tooltipWidth = tooltipRoot.offsetWidth ?? 0; const tooltipHeight = tooltipRoot.offsetHeight ?? 0; return getTooltipPosition(tooltipOptions.placement, absoluteLeft, absoluteTop, tooltipWidth, tooltipHeight); }; return (() => { var _el$ = _tmpl$2(); var _ref$ = tooltipRoot; typeof _ref$ === "function" ? use(_ref$, _el$) : tooltipRoot = _el$; insert(_el$, createComponent(Component, { u, seriesData, get cursor() { return cursor2(); }, get focusedSeries() { return bus.data.focusSeries; } })); effect((_p$) => { var _v$ = containerProps.id, _v$2 = containerProps.class, _v$3 = { position: "absolute", "z-index": containerProps.zIndex, left: `${position().left}px`, top: `${position().top}px`, "pointer-events": "none", ...containerProps.style }; _v$ !== _p$.e && setAttribute(_el$, "id", _p$.e = _v$); _v$2 !== _p$.t && className(_el$, _p$.t = _v$2); _p$.a = style(_el$, _v$3, _p$.a); return _p$; }, { e: void 0, t: void 0, a: void 0 }); return _el$; })(); } }); }; dispose = render(() => createComponent(TooltipRoot, {}), u.root); }, destroy: () => { dispose(); tooltipRoot?.remove(); } } }; }; }; export { cursor, focusSeries, legend, tooltip };