UNPKG

@dschz/solid-uplot

Version:

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

499 lines (493 loc) 17.7 kB
import { U as UplotPluginFactory, S as SolidUplotPluginBus } from '../createPluginBus-DdrjQANs.js'; import { C as CursorData, S as SeriesDatum } from '../getSeriesData-04wGQord.js'; import { Component, JSX } from 'solid-js'; import uPlot$1 from 'uplot'; import 'solid-js/store'; /** * Message structure for cursor plugin communication via the plugin bus. */ type CursorPluginMessage = { /** * The ID of the plot instance that originated this cursor message. * Used to identify which chart is the source of cursor updates. */ readonly sourceId?: string; /** * State object containing cursor data for each plot instance. * Key is the plot ID, value is the cursor data for that plot. */ readonly state: { [plotId: string]: CursorData | undefined; }; }; /** * The message bus interface for the cursor plugin. * Defines the structure of cursor-related data shared between plugins. */ type CursorPluginMessageBus = { /** * Cursor plugin message containing state and source information. */ cursor?: CursorPluginMessage; }; /** * Creates a cursor plugin that tracks and shares cursor position and state across charts. * * This plugin enables cursor synchronization between multiple charts and provides * cursor data to other plugins (like tooltips) via the plugin bus system. * * **Features:** * - Tracks cursor position and x-series data indices * - Transmits cursor data via plugin bus for cross-plugin synchronization * * **Usage:** * ```typescript * import { createPluginBus } from '@dschz/solid-uplot'; * import { cursor, type CursorPluginMessageBus } from '@dschz/solid-uplot/plugins'; * * const bus = createPluginBus<CursorPluginMessageBus>(); * * <SolidUplot * // ... other props * plugins={[cursor()]} * pluginBus={bus} * /> * ``` * * **Plugin Bus Data:** * The plugin populates `bus.data.cursor` with: * - `sourceId`: ID of the chart that last updated the cursor * - `state[plotId]`: Cursor data for each chart instance * * See {@link CursorPluginMessage} for the complete data structure. * * @returns A plugin factory function that creates the cursor plugin instance * * @example * ```typescript * // Use with other plugins that depend on cursor data * const plugins = [ * cursor(), * tooltip(MyTooltipComponent), * focusSeries() * ]; * ``` */ declare const cursor: () => UplotPluginFactory<CursorPluginMessageBus>; /** * Target a series by its label. */ type SeriesByLabel = { /** * The label of the series. */ readonly label: string; readonly index?: never; readonly zeroIndex?: never; }; /** * Target a series by its uPlot index. */ type SeriesByIndex = { readonly label?: never; /** * The index of the y-series as according to uPlot. * * For example, if we have 3 y-series, they would have the following uPlot-based indices: * - 1 * - 2 * - 3 * * Note that the x-series is not included in this indexing (which starts at index 0 within uPlot). */ readonly index: number; readonly zeroIndex?: never; }; /** * Target a series by its zero-based index. */ type SeriesByZeroIndex = { readonly label?: never; readonly index?: never; /** * The zero-based index of the series. * * This is the index of the y-series if we were to treat them with zero-based indexing. * * For example, if we have 3 y-series, they would have the following zero-based indices: * - 0 * - 1 * - 2 * * Note that the x-series is not included in this zero-based indexing. */ readonly zeroIndex: number; }; /** * Defines how to target a specific series for focusing. * Can target by label, uPlot index, or zero-based index. */ type SeriesFocusTarget = SeriesByLabel | SeriesByIndex | SeriesByZeroIndex; /** * Message structure for focus series plugin communication via the plugin bus. */ type FocusSeriesPluginMessage = { /** * The id of the source that triggered the focus series event. */ readonly sourceId: string; /** * The list of target series to focus. */ readonly targets: SeriesFocusTarget[]; }; /** * The message bus interface for the focus series plugin. * Defines the structure of focus series data shared between plugins. */ type FocusSeriesPluginMessageBus = { /** * Focus series plugin message containing source and target information. */ focusSeries?: FocusSeriesPluginMessage; }; /** * Configuration options for the focus series plugin. */ type FocusSeriesPluginOptions<T extends CursorPluginMessageBus & FocusSeriesPluginMessageBus> = { /** * The vertical distance in pixels required to focus a series. * If the cursor's Y position is within this threshold of a Y value, that series is considered focused. * * @default 5 */ readonly pxThreshold?: number; /** * The alpha value to set for unfocused series. * * @default 0.1 */ readonly unfocusedAlpha?: number; /** * The alpha value to set for focused series. * * @default 1 */ readonly focusedAlpha?: number; /** * Whether to rebuild the paths of the series during redraw. * When set to `false`, uPlot will use the cached Path2D objects for the series. * * @default false */ readonly rebuildPaths?: boolean; /** * * For charts that are configured with this plugin, this callback is used to determine if the chart * should redraw when the plugin bus updates. It is an additional condition to the base condition `cursor?.sourceId !== u.root.id` * which is always applied first to prevent the source chart from redrawing twice. * * This can be used to add filtering logic, such as only reacting to specific source charts. * * @default undefined (always redraw for non-source charts) */ readonly shouldRedrawOnBusUpdate?: (params: { /** The current chart instance (the one evaluating whether to redraw) */ readonly u: uPlot; /** Current cursor state from the bus (may be from another chart) */ readonly cursor: T["cursor"]; /** Current focus series state from the bus */ readonly focusSeries: T["focusSeries"]; }) => boolean; }; /** * Creates a focus series plugin that visually highlights series based on cursor proximity. * * This plugin dims non-focused series and highlights the series closest to the cursor position. * When the cursor is within the specified pixel threshold of a series data point, that series * becomes focused while others are dimmed. * * **Features:** * - Automatic series focusing based on cursor proximity * - Configurable pixel threshold for focus detection * - Cross-chart focus synchronization via plugin bus * - Smooth visual feedback with alpha transparency * - Support for targeting series by label, index, or zero-based index * * **Requirements:** * - Requires the cursor plugin to be active for position tracking * - Needs a plugin bus for cursor data communication * * **Usage:** * ```typescript * import { createPluginBus } from '@dschz/solid-uplot'; * import { cursor, focusSeries, type CursorPluginMessageBus, type FocusSeriesPluginMessageBus } from '@dschz/solid-uplot/plugins'; * * const bus = createPluginBus<CursorPluginMessageBus & FocusSeriesPluginMessageBus>(); * * <SolidUplot * // ... other props * plugins={[cursor(), focusSeries({ pxThreshold: 10 })]} * pluginBus={bus} * /> * ``` * * @param options - Configuration options for focus behavior * @returns A plugin factory function that creates the focus series plugin instance */ declare const focusSeries: (options?: FocusSeriesPluginOptions<CursorPluginMessageBus & FocusSeriesPluginMessageBus>) => UplotPluginFactory<CursorPluginMessageBus & FocusSeriesPluginMessageBus>; /** * Simple legend placement options - only top corners to avoid axis conflicts */ type LegendPlacement = "top-left" | "top-right"; /** * Props passed to the custom legend SolidJS component. * * The legend plugin automatically provides these props to your custom component, ensuring it always * receives the latest series data from the chart via the plugin bus system. This creates a reactive * data flow where series data changes immediately trigger legend updates with fresh data information. */ type LegendProps = { readonly u: uPlot$1; readonly seriesData: SeriesDatum[]; readonly bus: SolidUplotPluginBus<CursorPluginMessageBus & FocusSeriesPluginMessageBus>; }; type LegendRootProps = { /** * HTML id attribute for the legend root * @default "solid-uplot-legend-root" */ readonly id?: string; /** * CSS class name for the legend root */ readonly class?: string; /** * Inline styles for the legend root */ readonly style?: JSX.CSSProperties; /** * The z-index of the legend root * @default 10 */ readonly zIndex?: number; }; type LegendConfigOptions = { /** * Legend placement within the chart area * @default "top-left" */ readonly placement?: LegendPlacement; /** * Pixel offset from the chart edges * @default 8 */ readonly pxOffset?: number; }; type LegendPluginOptions = LegendRootProps & LegendConfigOptions; /** * Creates a simple, opinionated legend plugin that displays custom legend content * overlaid on the chart's drawing area. * * **Design Philosophy:** * - Opinionated positioning: only top-left or top-right corners * - Size-constrained: legend cannot exceed chart drawing area dimensions * - Layout-agnostic: user controls internal layout and styling * - Non-interfering: designed to work harmoniously with chart interactions * * **Features:** * - Positioned within u.over element (chart drawing area) * - Automatic size constraints to prevent overflow * - Scroll handling for overflowing content * - Plugin bus integration for reactive updates * * @param Component - SolidJS component to render as the legend content * @param options - Configuration options for legend behavior and styling * @returns A plugin factory function that creates the legend plugin instance */ declare const legend: (Component: Component<LegendProps>, options?: LegendPluginOptions) => UplotPluginFactory<CursorPluginMessageBus & FocusSeriesPluginMessageBus>; /** * Defines the preferred placement of the tooltip relative to the cursor position. */ type TooltipCursorPlacement = "top-left" | "top-right" | "bottom-left" | "bottom-right"; /** * Props passed to the custom tooltip SolidJS component. * * The tooltip plugin automatically provides these props to your custom component, * ensuring it always receives the latest cursor data from the cursor plugin via * the plugin bus system. This creates a reactive data flow where cursor movements * immediately trigger tooltip updates with fresh position and data information. */ type TooltipProps = { /** The uPlot instance for accessing chart configuration and data */ readonly u: uPlot$1; /** * Current cursor data including position and data index, automatically updated * by the cursor plugin and passed via the plugin bus system */ readonly cursor: CursorData; /** Array of series metadata for all series in the chart */ readonly seriesData: SeriesDatum[]; /** * Optional focus series data from the focusSeries plugin. * * This is automatically updated by the focusSeries plugin and passed via the plugin bus system. * You can use this to determine if a series is focused and apply different styling for content * in your tooltip component. */ readonly focusedSeries?: FocusSeriesPluginMessage; }; /** * Container styling and accessibility props for the tooltip wrapper. */ type TooltipRootProps = { /** * HTML id attribute for the tooltip root * * @default "solid-uplot-tooltip-root" */ readonly id?: string; /** * CSS class name for the tooltip root * * @default undefined */ readonly class?: string; /** * Inline styles for the tooltip root * * @default {} */ readonly style?: JSX.CSSProperties; /** * The z-index of the tooltip root * @default 20 */ readonly zIndex?: number; }; /** * Configuration options for tooltip behavior. */ type TooltipConfigOptions = { /** * Preferred corner of the tooltip (relative to the cursor) * @default "top-left" */ readonly placement?: TooltipCursorPlacement; /** * Use fixed positioning for the tooltip. Set to true when the chart is in a * fixed positioning context (like a dialog or modal) to prevent tooltip clipping. * @default false */ readonly fixed?: boolean; /** * Optional callback to process or modify the calculated tooltip position. * Receives the calculated position and placement preference, and should return a position object with the same structure. * Use this to implement custom positioning logic or constraints. * * @param position - The calculated position with left and top coordinates * @param placement - The placement preference that was used for calculation * @returns Modified position object with left and top coordinates * * @example * ```ts * onPositionCalculated: (position, placement) => ({ * left: Math.max(0, position.left), // Prevent negative positioning * top: placement.includes('top') ? position.top - 5 : position.top + 10 * }) * ``` */ readonly onPositionCalculated?: (position: { left: number; top: number; }, placement: TooltipCursorPlacement) => { left: number; top: number; }; }; /** * Combined options for the tooltip plugin including container props and behavior config. */ type TooltipPluginOptions = TooltipRootProps & TooltipConfigOptions; /** * Creates a tooltip plugin that displays custom content when hovering over charts. * * This plugin renders a custom SolidJS component as a tooltip that follows the cursor * and displays contextual information about the data point being hovered. * * **Features:** * - Custom SolidJS component rendering * - Automatic positioning with edge detection and flipping * - Scroll-aware positioning that works with page scrolling * - Configurable placement preferences * - Position callback for custom positioning logic or overrides * - Accessible tooltip with proper ARIA attributes * - Automatic cleanup and memory management * * **Requirements:** * - Requires the cursor plugin to be active for position tracking * - Needs a plugin bus for cursor data communication * * **Usage:** * ```typescript * import { createPluginBus } from '@dschz/solid-uplot'; * import { cursor, tooltip, type CursorPluginMessageBus, type TooltipProps } from '@dschz/solid-uplot/plugins'; * import type { Component } from 'solid-js'; * * const bus = createPluginBus<CursorPluginMessageBus>(); * * // Custom tooltip component MUST accept TooltipProps * const MyTooltip: Component<TooltipProps> = (props) => { * const xDate = () => new Date(props.cursor.xValue * 1000).toLocaleDateString(); * * return ( * <div style={{ * background: "white", border: "1px solid #ccc", padding: "8px", * "border-radius": "4px", "box-shadow": "0 2px 4px rgba(0,0,0,0.1)" * }}> * <div style={{ "font-weight": "bold", "margin-bottom": "8px" }}> * {xDate()} * </div> * <For each={props.seriesData}> * {(series) => { * // Fetch data value using series index and cursor position * const value = () => props.u.data[series.seriesIdx]?.[props.cursor.idx]; * * return ( * <Show when={series.visible}> * <div style={{ display: "flex", "align-items": "center", "margin-bottom": "4px" }}> * <div style={{ * width: "10px", height: "10px", "border-radius": "50%", * "background-color": series.stroke, "margin-right": "6px" * }} /> * <span style={{ color: series.stroke }}> * {series.label}: {value()?.toFixed(2)} * </span> * </div> * </Show> * ); * }} * </For> * </div> * ); * }; * * <SolidUplot * // ... other props * plugins={[cursor(), tooltip(MyTooltip, { placement: "top-right" })]} * pluginBus={bus} * /> * ``` * * **Custom Tooltip Component Requirements:** * - Must accept `TooltipProps` as props type (exported from '@dschz/solid-uplot/plugins') * - Must be a valid SolidJS Component * - Receives: `u` (uPlot instance), `cursor` (position/data), `seriesData` (series metadata) * - Use `props.u.data[series.seriesIdx][props.cursor.idx]` to fetch data values * * @param Component - SolidJS component to render as the tooltip content * @param options - Configuration options for tooltip behavior and styling * @returns A plugin factory function that creates the tooltip plugin instance */ declare const tooltip: (Component: Component<TooltipProps>, options?: TooltipPluginOptions) => UplotPluginFactory<CursorPluginMessageBus & FocusSeriesPluginMessageBus>; export { type CursorPluginMessage, type CursorPluginMessageBus, type FocusSeriesPluginMessage, type FocusSeriesPluginMessageBus, type LegendProps, type TooltipProps, cursor, focusSeries, legend, tooltip };