@dschz/solid-uplot
Version:
SolidJS wrapper for uPlot — ultra-fast, tiny time-series & charting library
499 lines (493 loc) • 17.7 kB
TypeScript
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 };