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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@shlomo-dana/interactive-chart

v0.1.1

Published

Generic React time-series chart. The host owns data, colors, labels, and units.

Readme


What it is

InteractiveChart is a reusable plot for numeric X/Y series (typically time on X). It is not tied to a product, a sensor list, or a backend.

| The library does | The host does | | --- | --- | | Draw lines and points at full resolution | Fetch / store samples | | Wheel zoom, pan, auto-scale, reset | Pick stroke colors | | Legend, toolbar, tooltip, grid, theme tokens | Pick labels and units (Time (s), Height (m), …) | | Keep deep zoom detail (no downsample) | Sort points by x |

Built on uPlot. Designed to ship as an npm package.


Install

Peer dependencies: React 18+. The package depends on uplot.

npm install interactive-chart

When the package is published under a scope, use that name instead, for example:

npm install @your-org/interactive-chart
import { InteractiveChart } from 'interactive-chart'

Until it is on the npm registry, this repo is a workspace package inside the host app (packages/interactive-chart).


Quick start

import { InteractiveChart } from 'interactive-chart'

export function AltitudePlot() {
  return (
    <div style={{ height: 420 }}>
      <InteractiveChart
        series={[
          {
            id: 'alt',
            label: 'Altitude',
            points: [
              { x: 0, y: 10 },
              { x: 12, y: 48 },
              { x: 30, y: 120 },
            ],
            style: { stroke: '#22c55e', width: 1.5 },
            mode: 'line',
          },
        ]}
        theme={{
          textColor: '#e6edf3',
          mutedTextColor: '#8b949e',
          gridColor: 'rgba(255,255,255,0.06)',
        }}
        xAxis={{ label: 'Time', unit: 's', hardMin: 0, constrainToData: true }}
        yAxis={{ label: 'Height', unit: 'm' }}
        legend={{ position: 'top-left', interactive: true }}
        toolbar={{ showAutoX: true, showAutoY: true, showReset: true }}
        interaction={{
          wheelZoom: { sensitivity: 'slow' },
          cursor: { crosshair: 'xy' },
        }}
      />
    </div>
  )
}

The chart fills its parent. Give the parent a real height (height: 100% inside a flex layout, or a fixed pixel height).


See it working

The library has no backend. The fastest way to check fill, stacking, zoom, and legend is a query-param demo on the host app (this repo’s MapApp). No CSV and no telemetry.

1. Start the host

From the MapApp repo root (the folder that contains packages/interactive-chart):

npm run dev -- --port 5178 --strictPort

Vite prints the URL (here: http://localhost:5178/). Keep the InteractiveChart window open.

2. Open a demo URL

| URL | What you should see | What to click | | --- | --- | --- | | /?chartFill=1 | Two overlapping fills (cyan on top of red) plus a pink line on top of both. Stroke stays solid; only the area is transparent. | Legend: hide Fill on top — the red band remains. Hide Fill behind — cyan stays. Wheel-zoom X; drag to pan. | | /?chartStress=1 | One dense line (~100k points, 0–600 s). A tall spike around 248.2 s is almost invisible until you zoom in. | Zoom X around 248 s until the spike is obvious. Reset / X restores the full range. | | / | Live Safety MFC series (Limit is filled). Empty until mission CSVs / telemetry exist. | Use this after the demos. |

Fill demo series are listed in the legend as Fill on top, Fill behind, Line only. Legend order is the array order; paint order is zIndex (cyan is first in the legend, but drawn above red).

Leave a demo: go back to / (no query).

3. How that switch is wired (so you can add another)

Host-only — not inside this package:

  1. Build synthetic ChartSeries[] (see createFillDemoSeries / createStressTimeSeries in the host).
  2. Gate it on a query flag, e.g. ?chartFill=1.
  3. Return that array before live data.
if (new URLSearchParams(location.search).get('chartFill') === '1') {
  return createFillDemoSeries()
}

To add a new check (log Y, points-only, zIndex: 'front', …): copy that pattern with a new flag (?chartFoo=1) and a new series factory. Reload the URL. No rebuild of telemetry is required.

4. Automated tests (no browser)

From the MapApp root:

npm test -- packages/interactive-chart/tests

That covers fill color/opacity, draw order (zIndex / 'front'), zoom, pan, and alignment. After API changes, rebuild types:

npm run build --prefix packages/interactive-chart
npx tsc -b

5. Paste-in playground (any React host)

Drop this in a page with a real height. Same idea as ?chartFill=1, without MapApp.

import { InteractiveChart } from 'interactive-chart'

const n = 80
const xs = Array.from({ length: n }, (_, i) => (i / (n - 1)) * 60)
const band = (amp: number, base: number) =>
  xs.map((x) => ({ x, y: base + amp * Math.sin(x / 5) }))

export function ChartPlayground() {
  return (
    <div style={{ height: 420 }}>
      <InteractiveChart
        series={[
          {
            id: 'top',
            label: 'Fill on top',
            points: band(10, 28),
            mode: 'area',
            zIndex: 1,
            style: { stroke: '#22d3ee', fill: { opacity: 0.4, to: 0 } },
          },
          {
            id: 'behind',
            label: 'Fill behind',
            points: band(16, 48),
            mode: 'area',
            zIndex: 0,
            style: { stroke: '#f87171', fill: { opacity: 0.28, to: 0 } },
          },
          {
            id: 'line',
            label: 'Line only',
            points: band(5, 10),
            style: { stroke: '#f472b6' },
          },
        ]}
        xAxis={{ label: 'Time', unit: 's', hardMin: 0 }}
        yAxis={{ label: 'Height', unit: 'm' }}
        legend={{ interactive: true }}
      />
    </div>
  )
}

Fill under the line

Turn a line into a shaded region from the curve down to a baseline (usually 0). The stroke stays solid; only the fill is transparent.

{
  id: 'limit',
  label: 'Limit',
  points: [{ x: 0, y: 80 }, { x: 30, y: 40 }],
  mode: 'area', // or keep 'line' and set fill
  style: {
    stroke: '#f87171',
    width: 1.5,
    fill: { opacity: 0.22, to: 0 },
  },
}

Shortcuts:

style: { stroke: '#22c55e', fill: true }                 // stroke color, 25% opacity, down to 0
style: { stroke: '#22c55e', fill: { opacity: 0.4 } }
style: { stroke: '#22c55e', fill: { color: '#22c55e', opacity: 0.15, to: 'scale' } }

to: 0 (default) fills down to height 0. to: 'scale' fills to the bottom of the current Y view.

Live check: See it working → /?chartFill=1.


Who paints on top

Filled areas overlap. The library stacks them so you do not have to reorder the series array (legend order stays as you passed it).

Default (seriesLayering: 'auto'):

back  →  fills  →  lines  →  points  →  front

Two fills with no zIndex keep array order: the later fill covers the earlier one.

Pin one series above or below the others with zIndex:

series={[
  {
    id: 'cover',
    label: 'Fill on top',
    points: bandA,
    mode: 'area',
    zIndex: 1, // or 'front' to sit above lines and points too
    style: { stroke: '#38bdf8', fill: { opacity: 0.4, to: 0 } },
  },
  {
    id: 'base',
    label: 'Fill behind',
    points: bandB,
    mode: 'area',
    zIndex: 0,
    style: { stroke: '#f87171', fill: { opacity: 0.28, to: 0 } },
  },
  {
    id: 'trace',
    label: 'Line only',
    points: trace,
    // no zIndex → line layer, above both fills
    style: { stroke: '#e879f9' },
  },
]}

| zIndex | Result | | --- | --- | | omitted | auto layer (fill / line / points) | | 0, 1, 2, … | among the same kind, higher covers lower | | 'front' | on top of everyone | | 'back' | behind everyone |

seriesLayering: 'input' turns off the fill/line/points default and uses array order only. Per-series zIndex still wins.

Live check: same /?chartFill=1 URL — cyan (zIndex: 1) covers red (zIndex: 0); the line stays above both.


Two color systems

Do not mix these up.

theme              → chrome (background, axes, grid, tooltip, toolbar, legend text)
series.style.stroke → the line / points themselves
<InteractiveChart
  theme={{
    textColor: '#e6edf3',
    gridColor: 'rgba(255,255,255,0.06)',
    tooltip: { background: 'rgba(22,24,28,0.94)', text: '#e6edf3' },
  }}
  series={[
    { id: 'a', label: 'A', points, style: { stroke: '#22c55e' } },
    { id: 'b', label: 'B', points, style: { stroke: '#f472b6' } },
  ]}
/>

If a series has no style.stroke, the library uses the theme text color — never a built-in neon palette.

Helpers: defaultDarkTheme, defaultLightTheme, resolveChartTheme.


Interactions

| Action | Result | | --- | --- | | Wheel over the plot | Zoom X, centered on the cursor | | Wheel over the Y axis | Zoom Y only | | Drag left–right | Pan X | | Double-click | Reset both axes to auto | | Toolbar X / Y | Auto-scale that axis | | Toolbar reset | Auto-scale both | | Click a legend item | Hide / show that series |

Wheel zoom defaults to 'slow' (deliberate, not jumpy). Use 'normal', 'fast', or a number if you want it snappier.

X and Y auto/manual are independent. Incoming data does not steal a manual zoom; press X or Y when you want the full range again.

hardMin / hardMax and constrainToData keep the viewport from sliding into empty space (for example time never below 0).


Series

{
  id: 'main',
  label: 'Main / INS',          // legend + tooltip
  points: [{ x: 1.2, y: 340 }], // already sorted by x
  mode: 'line' | 'points' | 'area',
  zIndex: 1,                    // higher covers lower; 'front' / 'back'
  visible: true,
  legend: true,                 // false = plot, skip legend
  spanGaps: true,
  style: {
    stroke: '#22c55e',
    width: 1.5,
    dash: [4, 3],
    opacity: 1,
    point: { show: true, size: 6, fill: 'transparent' },
  },
}
  • Points must be sorted by x. The chart does not re-sort or downsample.
  • Non-finite x / y are ignored.
  • legend: false still draws the series and includes it in auto-scale.

Props (overview)

| Prop | Role | | --- | --- | | series | Data to plot | | seriesLayering | 'auto' (fills behind lines) or 'input' (array order) | | theme | Chrome colors (CSS variables under the hood) | | xAxis / yAxis | label, unit, formatters, hardMin, padding, log Y | | legend | Position, interactive, optional “All” | | toolbar | Auto X, Auto Y, Reset | | grid | { x, y } dashed grid | | appearance | density: 'compact' \| 'comfortable', corner radius | | interaction | Zoom, pan, wheel, crosshair | | title | Optional heading inside the chart (hosts usually own the window title) |

Imperative handle:

ref.resetView()
ref.autoScale()
ref.autoScaleX()
ref.autoScaleY()
ref.getVisibleRange() // { xMin, xMax, yMin, yMax }

Axis titles render as Label (unit) — e.g. Height (m).


Layout notes for hosts

  • The component is a column flex child: width/height: 100%, min-width/min-height: 0.
  • Put it in a panel that already has a height.
  • ResizeObserver keeps the canvas in sync when the window is docked or stretched.

Repository layout

packages/interactive-chart/
├── README.md          ← you are here
├── package.json
├── docs/              ← screenshots
├── tests/             ← jest (run from the host: npm test -- packages/interactive-chart/tests)
└── src/
    ├── index.ts       ← public API
    ├── InteractiveChart.tsx
    ├── InteractiveChart.types.ts
    ├── InteractiveChart.css
    ├── core/          ← range, zoom, pan, alignment
    ├── interactions/  ← wheel + drag
    ├── theme/
    ├── ui/            ← legend, toolbar, tooltip, uPlot options
    └── hooks/

Nothing in src/ imports an application store, telemetry hub, or product palette.


License

MIT