npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@dschz/solid-uplot

v0.4.0

Published

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

Readme

@dschz/solid-uplot

License uPlot npm Bundle Size JSR CI Discord

💹 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

📁 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";
import type {
  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";
import type {
  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";
import type { 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";
import type { CursorPluginMessageBus, TooltipProps, LegendProps } from "@dschz/solid-uplot/plugins";

// Create a tooltip component
const MyTooltip = (props: TooltipProps) => (
  <div style={{ background: "white", padding: "8px", border: "1px solid #ccc" }}>
    <div>X: {props.cursor.xValue}</div>
    <For each={props.seriesData}>
      {(series) => {
        const value = props.u.data[series.seriesIdx]?.[props.cursor.idx];
        return (
          <div>
            {series.label}: {value}
          </div>
        );
      }}
    </For>
  </div>
);

// Create a legend component
const MyLegend = (props: LegendProps) => (
  <div style={{ background: "rgba(255,255,255,0.9)", padding: "8px" }}>
    <For each={props.seriesData}>
      {(series) => (
        <div style={{ display: "flex", "align-items": "center", gap: "4px" }}>
          <div
            style={{
              width: "12px",
              height: "12px",
              background: series.stroke,
            }}
          />
          <span>{series.label}</span>
        </div>
      )}
    </For>
  </div>
);

const MyChart = () => {
  const bus = createPluginBus<CursorPluginMessageBus>();

  return (
    <SolidUplot
      data={[
        [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:

<div style={{ width: "100%", height: "400px" }}>
  <SolidUplot
    autoResize={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 }) => <SolidUplot width={width} height={height} data={data} />}
</AutoSizer>;

🔌 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";
import type { CursorPluginMessageBus } from "@dschz/solid-uplot/plugins";

const cursorPlugin = 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";
import type { FocusSeriesPluginMessageBus } from "@dschz/solid-uplot/plugins";

const focusPlugin = 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";
import type { TooltipProps } from "@dschz/solid-uplot/plugins";

const MyTooltip: Component<TooltipProps> = (props) => {
  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" }}>X: {props.cursor.xValue}</div>
      <For each={props.seriesData}>
        {(series) => {
          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>
  );
};

const tooltipPlugin = 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";
import type { LegendProps } from "@dschz/solid-uplot/plugins";

const MyLegend: Component<LegendProps> = (props) => {
  // Access cursor data for interactive features
  const cursorVisible = () => props.bus.data.cursor?.state[props.u.root.id]?.visible;

  return (
    <div
      style={{
        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 active
        opacity: cursorVisible() ? 0.6 : 1,
        transition: "opacity 200ms",
      }}
    >
      <div style={{ "font-weight": "bold", "margin-bottom": "8px" }}>Legend</div>
      <For each={props.seriesData}>
        {(series) => (
          <Show when={series.visible}>
            <div
              style={{
                display: "flex",
                "align-items": "center",
                gap: "6px",
                "margin-bottom": "4px",
              }}
            >
              <div
                style={{
                  width: "12px",
                  height: "12px",
                  "background-color": series.stroke,
                  "border-radius": "2px",
                }}
              />
              <span style={{ "font-size": "14px" }}>{series.label}</span>
            </div>
          </Show>
        )}
      </For>
    </div>
  );
};

const legendPlugin = 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";
import type {
  CursorPluginMessageBus,
  FocusSeriesPluginMessageBus,
} from "@dschz/solid-uplot/plugins";

// Create a bus that includes all plugin message types
const bus = createPluginBus<CursorPluginMessageBus & FocusSeriesPluginMessageBus>();

const MyChart = () => {
  return (
    <SolidUplot
      data={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:

import type { UplotPluginFactory } from "@dschz/solid-uplot";
import type { CursorPluginMessageBus } from "@dschz/solid-uplot/plugins";

// 1. Define your plugin's message type
export type MyPluginMessage = {
  value: number;
  timestamp: number;
};

// 2. Define your plugin's message bus
export type MyPluginMessageBus = {
  myPlugin?: MyPluginMessage;
};

// 3. Export your plugin factory
export const myPlugin = (
  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 state
          bus.setData("myPlugin", {
            value: 0,
            timestamp: Date.now(),
          });
        },
        setData: (u) => {
          // Update plugin state
          bus.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";
import type { FocusSeriesPluginMessageBus } from "@dschz/solid-uplot/plugins";

const bus = createPluginBus<FocusSeriesPluginMessageBus>();

// External component that can trigger series focus
const DataGrid = (props: { bus: typeof bus }) => {
  const handleRowHover = (seriesLabel: string) => {
    props.bus.setData("focusSeries", {
      sourceId: "data-grid",
      targets: [{ label: seriesLabel }],
    });
  };

  return <table>{/* Grid implementation */}</table>;
};

// Chart and grid interact through shared bus
const MyDashboard = () => {
  return (
    <div>
      <DataGrid bus={bus} />
      <SolidUplot data={data} pluginBus={bus} plugins={[focusSeries()]} />
    </div>
  );
};

🔧 API Reference

SolidUplot Component

type SolidUplotEvents = {
  /** Callback fired when the uPlot instance is created */
  readonly onCreate?: (u: uPlot, meta: OnCreateMeta) => void;
  /** Callback fired when the cursor moves */
  readonly onCursorMove?: (params: OnCursorMoveParams) => void;
};

// Main component props (extends all uPlot.Options except plugins, width, height)
type SolidUplotProps<T extends VoidStruct = VoidStruct> = SolidUplotOptions<T> &
  SolidUplotEvents & {
    // Ref callback to access the chart container element
    ref?: Ref<HTMLDivElement>;

    // CSS class name for the chart container (default: "solid-uplot")
    // Additional classes will be appended to the default class
    class?: 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 enhancements
type SolidUplotOptions<T extends VoidStruct = VoidStruct> = Omit<
  uPlot.Options,
  "plugins" | "width" | "height"
> & {
  // Chart dimensions
  width?: number; // default: 600
  height?: number; // default: 300

  // Plugin configuration
  plugins?: SolidUplotPlugin<T>[];
  pluginBus?: SolidUplotPluginBus<T>;
};

// Plugin type (can be standard uPlot plugin or factory function)
type SolidUplotPlugin<T extends VoidStruct = VoidStruct> = uPlot.Plugin | UplotPluginFactory<T>;

Plugin Bus

// Plugin bus type (derived from createPluginBus return type)
type SolidUplotPluginBus<T extends VoidStruct = VoidStruct> = ReturnType<typeof createPluginBus<T>>;

// Create a plugin bus
const createPluginBus: <T extends VoidStruct = VoidStruct>(
  initialData?: T,
) => SolidUplotPluginBus<T>;

Built-in Plugin Options

// Cursor Plugin
const cursor = (): UplotPluginFactory<CursorPluginMessageBus>;

// Focus Series Plugin
const focusSeries = (options?: {
  pxThreshold?: number; // default: 15
}): UplotPluginFactory<CursorPluginMessageBus & FocusSeriesPluginMessageBus>;

// Tooltip Plugin
const tooltip = (
  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 Plugin
const legend = (
  Component: Component<LegendProps>,
  options?: {
    placement?: "top-left" | "top-right"; // default: "top-left"
    pxOffset?: number; // default: 8
    id?: string;
    class?: string;
    style?: JSX.CSSProperties;
    zIndex?: number; // default: 10
  }
): UplotPluginFactory<CursorPluginMessageBus & FocusSeriesPluginMessageBus>;

📚 Examples

Basic Chart

import { SolidUplot } from "@dschz/solid-uplot";

const BasicChart = () => {
  return (
    <SolidUplot
      data={[
        [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";
import type {
  CursorPluginMessageBus,
  FocusSeriesPluginMessageBus,
  TooltipProps,
  LegendProps,
} from "@dschz/solid-uplot/plugins";

const MyTooltip: Component<TooltipProps> = (props) => (
  <div style={{ background: "white", padding: "8px", border: "1px solid #ccc" }}>
    <div>Time: {new Date(props.cursor.xValue * 1000).toLocaleTimeString()}</div>
    <For each={props.seriesData}>
      {(series) => {
        const value = props.u.data[series.seriesIdx]?.[props.cursor.idx];
        return (
          <div style={{ color: series.stroke }}>
            {series.label}: {value?.toFixed(2)}
          </div>
        );
      }}
    </For>
  </div>
);

const MyLegend: Component<LegendProps> = (props) => {
  const cursorVisible = () => props.bus.data.cursor?.state[props.u.root.id]?.visible;

  return (
    <div
      style={{
        background: "white",
        border: "1px solid #ddd",
        padding: "8px",
        opacity: cursorVisible() ? 0.6 : 1,
        transition: "opacity 200ms",
      }}
    >
      <For each={props.seriesData}>
        {(series) => (
          <div style={{ display: "flex", "align-items": "center", gap: "6px" }}>
            <div
              style={{
                width: "12px",
                height: "12px",
                background: series.stroke,
              }}
            />
            <span>{series.label}</span>
          </div>
        )}
      </For>
    </div>
  );
};

const FullFeaturedChart = () => {
  const bus = createPluginBus<CursorPluginMessageBus & FocusSeriesPluginMessageBus>();

  return (
    <SolidUplot
      data={[
        [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

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

External Integration

const Dashboard = () => {
  const bus = createPluginBus<FocusSeriesPluginMessageBus>();

  const handleSeriesToggle = (seriesLabel: string) => {
    bus.setData("focusSeries", {
      sourceId: "external-control",
      targets: [{ label: seriesLabel }],
    });
  };

  return (
    <div>
      <div>
        <button onClick={() => handleSeriesToggle("Series 1")}>Focus Series 1</button>
        <button onClick={() => handleSeriesToggle("Series 2")}>Focus Series 2</button>
      </div>
      <SolidUplot data={data} plugins={[focusSeries()]} pluginBus={bus} />
    </div>
  );
};

🎮 Interactive Playground

This library includes a comprehensive playground application that demonstrates all features and provides interactive examples. The playground showcases:

  • Basic Charts: Simple line charts with different configurations
  • Plugin Examples: All built-in plugins working together
  • Legend Showcase: Various legend patterns and interactions
  • Responsive Sizing: Auto-resize and manual sizing examples
  • Custom Plugins: Examples of creating your own plugins
  • External Integration: Charts interacting with external components

Running the Playground Locally

To explore the playground and see the library in action:

# Clone the repository
git clone https://github.com/dsnchz/solid-uplot.git
cd solid-uplot

# Install dependencies
npm install
# or
pnpm install
# or
yarn install
# or
bun install

# Start the playground development server
npm run start
# or
pnpm start
# or
yarn start
# or
bun start

The playground will be available at http://localhost:3000 and includes:

  • Live code examples with syntax highlighting
  • Interactive demos you can modify in real-time
  • Performance comparisons between different configurations
  • Best practices and common patterns
  • Plugin development examples with step-by-step guides

The playground source code also serves as a comprehensive reference for implementing various chart patterns and plugin combinations.

🤝 Contributing

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

Also check out the Discord community.

📄 License

MIT