Skip to content

Repository files navigation

@dschz/solid-uplot banner

@dschz/solid-uplot

LicenseuPlotnpmBundle SizeJSRCIDiscord

💹 SolidJS wrapper for uPlot — an ultra-fast, small footprint charting library for time-series data.

✨ Features

  • ✅ Fully reactive SolidJS wrapper around uPlot
  • 🔌 Plugin system support with inter-plugin communication
  • 🎯 Fine-grained control over chart lifecycle
  • 💡 Lightweight and fast
  • 💻 TypeScript support out of the box
  • 🎨 Built-in plugins for tooltips, legends, cursor tracking, and series focusing
  • 📱 Responsive sizing support with auto-resize capabilities

📦 Installation

npm install solid-js uplot @dschz/solid-uplot
pnpm install solid-js uplot @dschz/solid-uplot
yarn install solid-js uplot @dschz/solid-uplot
bun install solid-js uplot @dschz/solid-uplot

🎮 Live Demo

This repo ships with a live, running playground — a full showcase of every feature in the library. It's the fastest way to see solid-uplot in action and explore real, working examples of charts, plugins, responsive sizing, and external integrations.

Running the Playground

git clone https://github.com/dsnchz/solid-uplot.git
cd solid-uplot
# Install dependencies
bun install
# Start the playground
bun dev

The playground runs at http://localhost:3000 and includes interactive demos for:

  • All built-in plugins (cursor, tooltip, legend, focus series)
  • Responsive and auto-resize chart patterns
  • Custom plugin development
  • External component integration via the plugin bus

📁 Package Structure

This package provides three main export paths for different functionality:

@dschz/solid-uplot

Core components and plugin system:

import{SolidUplot,createPluginBus}from"@dschz/solid-uplot";importtype{SolidUplotPluginBus,UplotPluginFactory,UplotPluginFactoryContext,}from"@dschz/solid-uplot";

@dschz/solid-uplot/plugins

This export path provides four plugins (three of which can be considered primitives).

  • cursor: transmits cursor position data
  • focusSeries: transmits which series are visually emphasized
  • tooltip: plugin that allows you to present a custom JSX tooltip around the cursor
  • legend: plugin that allows you to present a custom JSX component as your legend over the canvas drawing area.
import{cursor,tooltip,legend,focusSeries}from"@dschz/solid-uplot/plugins";importtype{CursorPluginMessageBus,FocusSeriesPluginMessageBus,TooltipProps,LegendProps,}from"@dschz/solid-uplot/plugins";

@dschz/solid-uplot/utils

Some convenience utility functions for getting certain bits of data from a uPlot instance (except for getColorString which translates a series' stroke or fill into a color value).

import{getSeriesData,getCursorData,getColorString,getNewCalendarDayIndices,}from"@dschz/solid-uplot/utils";importtype{SeriesDatum,CursorData}from"@dschz/solid-uplot/utils";

🚀 Quick Start

import{SolidUplot,createPluginBus}from"@dschz/solid-uplot";import{cursor,tooltip,legend}from"@dschz/solid-uplot/plugins";importtype{CursorPluginMessageBus,TooltipProps,LegendProps}from"@dschz/solid-uplot/plugins";// Create a tooltip componentconstMyTooltip=(props: TooltipProps)=>(<divstyle={{background: "white",padding: "8px",border: "1px solid #ccc"}}><div>X: {props.cursor.xValue}</div><Foreach={props.seriesData}>{(series)=>{constvalue=props.u.data[series.seriesIdx]?.[props.cursor.idx];return(<div>{series.label}: {value}</div>);}}</For></div>);// Create a legend componentconstMyLegend=(props: LegendProps)=>(<divstyle={{background: "rgba(255,255,255,0.9)",padding: "8px"}}><Foreach={props.seriesData}>{(series)=>(<divstyle={{display: "flex","align-items": "center",gap: "4px"}}><divstyle={{width: "12px",height: "12px",background: series.stroke,}}/><span>{series.label}</span></div>)}</For></div>);constMyChart=()=>{constbus=createPluginBus<CursorPluginMessageBus>();return(<SolidUplotdata={[[0,1,2,3],// x values[10,20,30,40],// y values for series 1[15,25,35,45],// y values for series 2]}width={600}height={400}series={[{},{label: "Series 1",stroke: "#1f77b4"},{label: "Series 2",stroke: "#ff7f0e"},]}plugins={[cursor(),tooltip(MyTooltip),legend(MyLegend,{placement: "top-right",pxOffset: 12}),]}pluginBus={bus}/>);};

📏 Responsive Sizing

For responsive charts that automatically adapt to container size changes, use the autoResize prop:

<divstyle={{width: "100%",height: "400px"}}><SolidUplotautoResize={true}data={data}series={series}// Chart will automatically resize to fill the container/></div>

For more advanced responsive patterns, you can pair this library with @dschz/solid-auto-sizer:

npm install @dschz/solid-auto-sizer
pnpm install @dschz/solid-auto-sizer
yarn install @dschz/solid-auto-sizer
bun install @dschz/solid-auto-sizer
import{AutoSizer}from"@dschz/solid-auto-sizer";<AutoSizer>{({ width, height })=><SolidUplotwidth={width}height={height}data={data}/>}</AutoSizer>;

Alternatively, you can use createElementSize from @solid-primitives/resize-observer for a signal-based approach:

npm install @solid-primitives/resize-observer
import{createElementSize}from"@solid-primitives/resize-observer";constResponsiveChart=()=>{letcontainer!: HTMLDivElement;constsize=createElementSize(()=>container);return(<divstyle={{width: "100%",height: "400px"}}><divref={container}style={{width: "100%",height: "100%"}}><SolidUplotdata={data}width={size.width??0}height={size.height??0}series={series}/></div></div>);};

🔌 Enhanced Plugin System

The cornerstone feature of SolidUplot is its refined plugin system that enables extensible functionality and inter-plugin communication through a reactive message bus.

Plugin Bus Architecture

The Plugin Bus System enables plugins to communicate with each other and external components through a reactive store. This architecture provides:

  • Type-safe communication: All plugin messages are fully typed
  • Reactive updates: Changes in plugin state automatically trigger updates
  • Decoupled components: Plugins can interact without direct dependencies
  • Extensible: Easy to add new plugins that integrate with existing ones

Built-in Plugins

Cursor Plugin

Tracks cursor position and interaction state across charts:

import{cursor}from"@dschz/solid-uplot/plugins";importtype{CursorPluginMessageBus}from"@dschz/solid-uplot/plugins";constcursorPlugin=cursor();

The cursor plugin provides cursor position data that other plugins can consume through the bus.

Focus Series Plugin

Highlights series based on cursor proximity:

import{focusSeries}from"@dschz/solid-uplot/plugins";importtype{FocusSeriesPluginMessageBus}from"@dschz/solid-uplot/plugins";constfocusPlugin=focusSeries({pxThreshold: 15,// Distance threshold for focusing (default: 15)});

Tooltip Plugin

Renders custom tooltips with automatic positioning and overflow handling:

import{tooltip}from"@dschz/solid-uplot/plugins";importtype{TooltipProps}from"@dschz/solid-uplot/plugins";constMyTooltip: Component<TooltipProps>=(props)=>{return(<divstyle={{background: "white",border: "1px solid #ccc",padding: "8px","border-radius": "4px","box-shadow": "0 2px 4px rgba(0,0,0,0.1)",}}><divstyle={{"font-weight": "bold","margin-bottom": "8px"}}>X: {props.cursor.xValue}</div><Foreach={props.seriesData}>{(series)=>{constvalue=()=>props.u.data[series.seriesIdx]?.[props.cursor.idx];return(<Showwhen={series.visible}><divstyle={{display: "flex","align-items": "center","margin-bottom": "4px"}}><divstyle={{width: "10px",height: "10px","border-radius": "50%","background-color": series.stroke,"margin-right": "6px",}}/><spanstyle={{color: series.stroke}}>{series.label}: {value()?.toFixed(2)}</span></div></Show>);}}</For></div>);};consttooltipPlugin=tooltip(MyTooltip,{placement: "top-left",// "top-left" | "top-right" | "bottom-left" | "bottom-right"zIndex: 20,});

Legend Plugin

Adds customizable legends with smart positioning and interactive features:

import{legend}from"@dschz/solid-uplot/plugins";importtype{LegendProps}from"@dschz/solid-uplot/plugins";constMyLegend: Component<LegendProps>=(props)=>{// Access cursor data for interactive featuresconstcursorVisible=()=>props.bus.data.cursor?.state[props.u.root.id]?.visible;return(<divstyle={{background: "white",border: "1px solid #ddd","border-radius": "4px",padding: "8px","box-shadow": "0 2px 4px rgba(0,0,0,0.1)",// Dim when tooltip is activeopacity: cursorVisible() ? 0.6 : 1,transition: "opacity 200ms",}}><divstyle={{"font-weight": "bold","margin-bottom": "8px"}}>Legend</div><Foreach={props.seriesData}>{(series)=>(<Showwhen={series.visible}><divstyle={{display: "flex","align-items": "center",gap: "6px","margin-bottom": "4px",}}><divstyle={{width: "12px",height: "12px","background-color": series.stroke,"border-radius": "2px",}}/><spanstyle={{"font-size": "14px"}}>{series.label}</span></div></Show>)}</For></div>);};constlegendPlugin=legend(MyLegend,{placement: "top-left",// "top-left" | "top-right"pxOffset: 8,// Distance from chart edges (default: 8)zIndex: 10,});

Legend Plugin Features:

  • Simple positioning: Only top-left or top-right corners to avoid axis conflicts
  • Size-constrained: Legend cannot exceed chart drawing area dimensions
  • Layout-agnostic: You control internal layout and styling
  • Non-interfering: Designed to work harmoniously with chart interactions
  • Plugin bus integration: Access cursor and focus data for smart interactions
  • Automatic cleanup: Proper memory management and DOM cleanup

Plugin Bus Type Safety

When using multiple plugins, ensure type safety by properly typing the plugin bus:

import{createPluginBus}from"@dschz/solid-uplot";importtype{CursorPluginMessageBus,FocusSeriesPluginMessageBus,}from"@dschz/solid-uplot/plugins";// Create a bus that includes all plugin message typesconstbus=createPluginBus<CursorPluginMessageBus&FocusSeriesPluginMessageBus>();constMyChart=()=>{return(<SolidUplotdata={data}pluginBus={bus}plugins={[cursor(),focusSeries(),tooltip(MyTooltip),legend(MyLegend)]}/>);};

Creating Custom Plugins

The plugin system is open to extension. When authoring plugins for public consumption, follow this pattern:

importtype{UplotPluginFactory}from"@dschz/solid-uplot";importtype{CursorPluginMessageBus}from"@dschz/solid-uplot/plugins";// 1. Define your plugin's message typeexporttypeMyPluginMessage={value: number;timestamp: number;};// 2. Define your plugin's message busexporttypeMyPluginMessageBus={myPlugin?: MyPluginMessage;};// 3. Export your plugin factoryexportconstmyPlugin=(options={},): UplotPluginFactory<CursorPluginMessageBus&MyPluginMessageBus>=>{return({ bus })=>{if(!bus){console.warn("[my-plugin]: A plugin bus is required");return{hooks: {}};}return{hooks: {ready: (u)=>{// Initialize plugin statebus.setData("myPlugin",{value: 0,timestamp: Date.now(),});},setData: (u)=>{// Update plugin statebus.setData("myPlugin","value",(prev)=>prev+1);},},};};};

External Component Integration

The plugin bus enables powerful integrations between charts and external components:

import{createPluginBus}from"@dschz/solid-uplot";importtype{FocusSeriesPluginMessageBus}from"@dschz/solid-uplot/plugins";constbus=createPluginBus<FocusSeriesPluginMessageBus>();// External component that can trigger series focusconstDataGrid=(props: {bus: typeofbus})=>{consthandleRowHover=(seriesLabel: string)=>{props.bus.setData("focusSeries",{sourceId: "data-grid",targets: [{label: seriesLabel}],});};return<table>{/* Grid implementation */}</table>;};// Chart and grid interact through shared busconstMyDashboard=()=>{return(<div><DataGridbus={bus}/><SolidUplotdata={data}pluginBus={bus}plugins={[focusSeries()]}/></div>);};

🔧 API Reference

SolidUplot Component

typeSolidUplotEvents={/** Callback fired when the uPlot instance is created */readonlyonCreate?: (u: uPlot,meta: OnCreateMeta)=>void;/** Callback fired when the cursor moves */readonlyonCursorMove?: (params: OnCursorMoveParams)=>void;};// Main component props (extends all uPlot.Options except plugins, width, height)typeSolidUplotProps<TextendsVoidStruct=VoidStruct>=SolidUplotOptions<T>&SolidUplotEvents&{// Ref callback to access the chart container elementref?: Ref<HTMLDivElement>;// CSS class name for the chart container (default: "solid-uplot")// Additional classes will be appended to the default classclass?: string;// CSS styles for the chart container (position is managed internally)style?: Omit<JSX.CSSProperties,"position">;// Enable automatic resizing to fit container (default: false)autoResize?: boolean;// Whether to reset scales when chart data is updated (default: true)resetScales?: boolean;// Where to place children components relative to the chart (default: "top")childrenPlacement?: "top"|"bottom";};// Configuration options extending uPlot.Options with SolidJS enhancementstypeSolidUplotOptions<TextendsVoidStruct=VoidStruct>=Omit<uPlot.Options,"plugins"|"width"|"height">&{// Chart dimensionswidth?: number;// default: 600height?: number;// default: 300// Plugin configurationplugins?: SolidUplotPlugin<T>[];pluginBus?: SolidUplotPluginBus<T>;};// Plugin type (can be standard uPlot plugin or factory function)typeSolidUplotPlugin<TextendsVoidStruct=VoidStruct>=uPlot.Plugin|UplotPluginFactory<T>;

Plugin Bus

// Plugin bus type (derived from createPluginBus return type)typeSolidUplotPluginBus<TextendsVoidStruct=VoidStruct>=ReturnType<typeofcreatePluginBus<T>>;// Create a plugin busconstcreatePluginBus: <TextendsVoidStruct=VoidStruct>(initialData?: T,)=>SolidUplotPluginBus<T>;

Built-in Plugin Options

// Cursor Pluginconstcursor=(): UplotPluginFactory<CursorPluginMessageBus>;// Focus Series PluginconstfocusSeries=(options?: {pxThreshold?: number;// default: 15}): UplotPluginFactory<CursorPluginMessageBus&FocusSeriesPluginMessageBus>;// Tooltip Pluginconsttooltip=(Component: Component<TooltipProps>,options?: {placement?: "top-left"|"top-right"|"bottom-left"|"bottom-right";id?: string;class?: string;style?: JSX.CSSProperties;zIndex?: number;// default: 20}): UplotPluginFactory<CursorPluginMessageBus&FocusSeriesPluginMessageBus>;// Legend Pluginconstlegend=(Component: Component<LegendProps>,options?: {placement?: "top-left"|"top-right";// default: "top-left"pxOffset?: number;// default: 8id?: string;class?: string;style?: JSX.CSSProperties;zIndex?: number;// default: 10}): UplotPluginFactory<CursorPluginMessageBus&FocusSeriesPluginMessageBus>;

📚 Examples

Basic Chart

import{SolidUplot}from"@dschz/solid-uplot";constBasicChart=()=>{return(<SolidUplotdata={[[0,1,2,3],[10,20,30,40],[15,25,35,45],]}width={600}height={400}scales={{x: {time: false},}}series={[{},{label: "Series 1",stroke: "#1f77b4"},{label: "Series 2",stroke: "#ff7f0e"},]}/>);};

Chart with All Plugins

import{SolidUplot,createPluginBus}from"@dschz/solid-uplot";import{cursor,tooltip,legend,focusSeries}from"@dschz/solid-uplot/plugins";importtype{CursorPluginMessageBus,FocusSeriesPluginMessageBus,TooltipProps,LegendProps,}from"@dschz/solid-uplot/plugins";constMyTooltip: Component<TooltipProps>=(props)=>(<divstyle={{background: "white",padding: "8px",border: "1px solid #ccc"}}><div>Time: {newDate(props.cursor.xValue*1000).toLocaleTimeString()}</div><Foreach={props.seriesData}>{(series)=>{constvalue=props.u.data[series.seriesIdx]?.[props.cursor.idx];return(<divstyle={{color: series.stroke}}>{series.label}: {value?.toFixed(2)}</div>);}}</For></div>);constMyLegend: Component<LegendProps>=(props)=>{constcursorVisible=()=>props.bus.data.cursor?.state[props.u.root.id]?.visible;return(<divstyle={{background: "white",border: "1px solid #ddd",padding: "8px",opacity: cursorVisible() ? 0.6 : 1,transition: "opacity 200ms",}}><Foreach={props.seriesData}>{(series)=>(<divstyle={{display: "flex","align-items": "center",gap: "6px"}}><divstyle={{width: "12px",height: "12px",background: series.stroke,}}/><span>{series.label}</span></div>)}</For></div>);};constFullFeaturedChart=()=>{constbus=createPluginBus<CursorPluginMessageBus&FocusSeriesPluginMessageBus>();return(<SolidUplotdata={[[0,1,2,3,4,5],[10,20,30,40,50,60],[15,25,35,45,55,65],[5,15,25,35,45,55],]}width={800}height={500}series={[{},{label: "Revenue",stroke: "#1f77b4"},{label: "Profit",stroke: "#ff7f0e"},{label: "Expenses",stroke: "#2ca02c"},]}plugins={[cursor(),focusSeries({pxThreshold: 20}),tooltip(MyTooltip,{placement: "top-right"}),legend(MyLegend,{placement: "top-left",pxOffset: 12}),]}pluginBus={bus}/>);};

Responsive Chart

constResponsiveChart=()=>{return(<divstyle={{width: "100%",height: "400px",border: "1px solid #ccc"}}><SolidUplotautoResize={true}data={data}series={series}plugins={[cursor(),tooltip(MyTooltip)]}/></div>);};

External Integration

constDashboard=()=>{constbus=createPluginBus<FocusSeriesPluginMessageBus>();consthandleSeriesToggle=(seriesLabel: string)=>{bus.setData("focusSeries",{sourceId: "external-control",targets: [{label: seriesLabel}],});};return(<div><div><buttononClick={()=>handleSeriesToggle("Series 1")}>Focus Series 1</button><buttononClick={()=>handleSeriesToggle("Series 2")}>Focus Series 2</button></div><SolidUplotdata={data}plugins={[focusSeries()]}pluginBus={bus}/></div>);};

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Also check out the Discord community.

📄 License

MIT

About

SolidJS wrapper for uPlot — an ultra-fast, tiny time-series & charting library with a SolidJS enhanced plugin system

Topics

Resources

Stars

24 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages