UNPKG

@dschz/solid-uplot

Version:

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

335 lines (330 loc) 10.9 kB
import { getCursorData, getSeriesData } from "../chunk/X3NAOFDO.jsx"; // 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); } } }; }; }; // src/plugins/focusSeries.ts import { createEffect, createRoot } from "solid-js"; 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); } } }; }; }; // src/plugins/legend.tsx import { mergeProps, splitProps } from "solid-js"; import { render } from "solid-js/web"; import "uplot"; 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 <div ref={legendRoot} id={containerProps.id} class={containerProps.class} role="group" aria-label="Chart legend" style={containerStyle()} > <Component u={u} seriesData={seriesData} bus={bus} /> </div>; }; dispose = render(() => <LegendRoot />, u.over); }, destroy: () => { dispose(); legendRoot?.remove(); } } }; }; }; // src/plugins/tooltip.tsx import { mergeProps as mergeProps2, Show, splitProps as splitProps2 } from "solid-js"; import { render as render2 } from "solid-js/web"; import "uplot"; 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 = mergeProps2( { placement: "top-left", id: "solid-uplot-tooltip-root", style: {}, zIndex: 20 }, options ); const chartCursorData = () => bus.data.cursor?.state[u.root.id]; const [tooltipOptions, containerProps] = splitProps2(_options, ["placement"]); return <Show when={chartCursorData()}> {(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 <div ref={tooltipRoot} id={containerProps.id} class={containerProps.class} role="tooltip" aria-label="Chart tooltip" style={{ position: "absolute", "z-index": containerProps.zIndex, left: `${position().left}px`, top: `${position().top}px`, "pointer-events": "none", ...containerProps.style }} > <Component u={u} seriesData={seriesData} cursor={cursor2()} focusedSeries={bus.data.focusSeries} /> </div>; }} </Show>; }; dispose = render2(() => <TooltipRoot />, u.root); }, destroy: () => { dispose(); tooltipRoot?.remove(); } } }; }; }; export { cursor, focusSeries, legend, tooltip };