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

@spider-analyzer/timeline

v5.0.17

Published

React graphical component to display metric over time with a time selection feature.

Readme

TimeLine

React graphical component to display metrics over time with an interactive time-selection cursor, zoom, pan, and an optional quality line.

  • Drag & pan to shift time
  • Scroll to zoom
  • Drag to move, resize or redraw the time selection
  • Built-in zoom-out undo (no host-managed stack required)
  • Themable through plain CSS classes — no CSS-in-JS peer dep

Live example: https://timeline.oss.spider-analyzer.io

alt text alt text

Content

Features

Displaying metrics

  • TimeLine displays the evolution of metric(s) through time.
  • The time displayed has a min and max time, called hereunder a domain.
  • 1 to n stacked metrics per bar — defined by metricsDefinition.
  • Colors of the histogram bars are defined in metricsDefinition.colors[].
  • Metric names are displayed to the left of the chart.
  • The maximum value in the visible window is shown at the top of the y-axis.
  • Current time is displayed as a vertical arrow on the x-axis.

Quality line

  • Optional secondary line below the chart showing a per-slot quality value.
  • May have its own x-axis granularity, independent of the histogram.
  • Custom tooltip content per slot.

Selecting time

  • The cursor has handles to resize it via drag-and-drop.
  • If dragged outside the domain, the domain shifts.
  • Cursor can be redrawn by clicking and dragging over the chart.

Zooming

  • Scroll — zooms on the mouse position (factor 4 on zoom-in).
  • Double-click on the cursor — zooms over the selection.
  • Zoom-in/out icons on the cursor / x-axis.
  • Zoom-out undo — zooming out after zooming in returns to the exact previous view (internal breadcrumb, transparent to the host).

Limits:

  • Zoom-in stops at 15 px = smallestResolution.
  • Zoom-out stops at biggestVisibleDomain.

Dragging the domain

  • Ctrl + drag shifts the visible window.
  • Respects maxDomain if set.

Buttons

  • Double arrow icons on either side of the x-axis slide the domain.
  • Right-side icons: reset, goto-now.

Data refresh limits

  • Zoom is disabled while a fetch is in progress.
  • Resize triggers a refresh only every 30 px — avoids flooding the API.

Design considerations

TimeLine is designed for integration into time-series and operational reporting UIs. It pairs naturally with a grid of records, using the cursor to select the time range of interest.

When integrating, you define:

  • Metrics to display + their colors and legend: metricsDefinition.
  • The initial window: return it from onLoadDefaultDomain, or pass it directly as domain.
  • Optional outer bounds: maxDomain.
  • Zoom limits: biggestVisibleDomain, smallestResolution.
  • Reset behavior: onResetTime (typically clears domain so the component re-asks for a default).
  • Formatters for x-axis, tooltips, and metric values.
  • Required time zone (IANA name): timeZone.

Installation

npm install --save @spider-analyzer/timeline

Import the stylesheet once in your entry:

import '@spider-analyzer/timeline/dist/timeline.css';
import '@spider-analyzer/timeline/dist/tipDark.css';

No moment / MUI / lodash peer deps. luxon is declared as a dependency but is already on most modern React apps; if not, npm will pull it in transitively.

Integration example

<TimeLine
  className="my-timeline"
  timeZone="Europe/Paris"                   /* required */
  timeSpan={this.state.timeSpan}            /* { start: Date, stop: Date } */
  histo={{
    items: this.state.items,                /* [{ time: Date, metrics: number[], total: number }] */
    intervalMs: this.state.intervalMs,      /* number */
  }}
  showHistoToolTip
  quality={{
    items: this.state.quality,              /* [{ time: Date, quality: 0..1, tip?: ReactNode }] */
    intervalMin: this.state.intervalMin,
  }}
  zoomOutFactor={1.75}
  domain={this.state.domain}                /* { min: Date, max: Date } | null */
  maxDomain={this.state.maxDomain}
  metricsDefinition={metricsDefinition}
  biggestVisibleDomain={30 * 24 * 60 * 60 * 1000}  /* 1 month in ms */
  biggestTimeSpan={24 * 60 * 60 * 1000}            /* 1 day in ms */
  smallestResolution={60 * 1000}                   /* 1 minute in ms */
  labels={{ backwardButtonTip: 'Slide into the past' }}
  tools={{ gotoNow: false }}
  showLegend
  fetchWhileSliding
  selectBarOnClick

  onLoadDefaultDomain={this.onLoadDefaultDomain}   /* returns Domain | Promise<Domain> */
  onLoadHisto={this.onLoadHisto}                   /* ({ intervalMs, start, end }) */
  onTimeSpanChange={this.onTimeSpanChange}         /* ({ start, stop }) */
  onShowMessage={console.log}
  onDomainChange={this.onDomainChange}             /* (domain) */
  onResetTime={this.onResetTime}
  onFormatTimeToolTips={this.onFormatTimeToolTips}
  onFormatTimeLegend={multiFormat}
  onFormatMetricLegend={formatNumber}
/>

TimeLine is a controlled component.

Props

| Prop | Type | Required | Notes | |------------------------|-------------------------------------------------------------------------------------------|----------|--------------------------------------------------------------------------------| | timeZone | string (IANA) | yes | Threads into the internal tz-aware time engine for formatting. | | domain | { min: Date, max: Date } \| null | yes | Pass null on first render to defer to onLoadDefaultDomain. | | timeSpan | { start: Date, stop: Date } | yes | The cursor selection. | | histo | { items: HistoItem[], intervalMs: number \| null } | yes | items[].time is Date. items and intervalMs MUST be updated atomically. | | metricsDefinition | { count, legends, colors } | yes | See example below. | | maxDomain | { min?: Date, max?: Date } | no | Hard outer bounds for pan. | | biggestVisibleDomain | number (ms) | no | Max visible window; caps zoom-out. | | biggestTimeSpan | number (ms) | no | Max selectable cursor span. | | smallestResolution | number (ms) | yes | Floor for zoom-in (15 px == this duration). | | quality | { items: QualityItem[], intervalMin?: number } | no | Optional quality line below the chart. | | qualityScale | (q: number) => string | no | Color for a quality value. Defaults to a red→green scale. | | zoomOutFactor | number | no | Default 1.25. Used only when the internal breadcrumb is empty. | | showHistoToolTip | boolean | no | | | HistoToolTip | React component | no | Custom tooltip content — see below. | | className | string | no | Applied to the outer container. | | classes | Record<slot, string> | no | Override per-slot class names. See Styling. | | rcToolTipPrefixCls | string | no | Override rc-tooltip class prefix. | | margin | { left?, right?, top?, bottom? } numbers | no | | | xAxis, yAxis | axis config objects (see below) | no | | | tools | { slideForward?, slideBackward?, resetTimeline?, gotoNow?, cursor?, zoomIn?, zoomOut? } | no | Toggle individual tools. | | fetchWhileSliding | boolean | no | Refetch while panning. | | selectBarOnClick | boolean | no | Clicking a histogram bar snaps the cursor to it. | | labels | Record<string, string> | no | Translations / message overrides. | | showLegend | boolean | no | Defaults to true. |

xAxis / yAxis:

xAxis?: {
  arrowPath?: string;
  spaceBetweenTicks: number;
  barsBetweenTicks: number;
  showGrid?: boolean;
  height?: number;
};
yAxis?: {
  arrowPath?: string;
  spaceBetweenTicks?: number;
  showGrid?: boolean;
};

metricsDefinition:

{
  count: 3,
  legends: ['Info', 'Warn', 'Fail'],
  colors: [
    { fill: '#9be18c', stroke: '#5db352', text: '#5db352' },
    { fill: '#f6bc62', stroke: '#e69825', text: '#e69825' },
    { fill: '#ff5d5a', stroke: '#f6251e', text: '#f6251e' },
  ],
}

Custom HistoToolTip props:

HistoToolTip.propTypes = {
  classes: object,
  item: {
    start: Date,
    end:   Date,
    x1:    number,
    x2:    number,
    metrics: number[],
    total:   number,
  },
  metricsDefinition: object,
  onFormatTimeToolTips: (time: Date) => ReactNode,
  onFormatMetricLegend: (value: number) => string,
};

Callbacks

onLoadDefaultDomain(): Domain | Promise<Domain> | void

Called on mount when domain is null. Return the default domain synchronously or asynchronously; the component seeds its internal stack from the returned value and emits onDomainChange once resolved.

onLoadHisto({ intervalMs, start, end }): void

Called when the component needs histogram data. Fires on mount (if the domain is known), on domain changes, on width changes, and on pan if fetchWhileSliding is set.

onTimeSpanChange({ start, stop }): void

Called when the user resizes, moves, or redraws the cursor.

onDomainChange(domain): void

Called whenever the visible domain changes — zoom in/out, pan, or a cursor-triggered edge shift. Host updates its domain state.

onResetTime(): void

Called when the user clicks the reset tool. Typical handler: clear the host domain and let onLoadDefaultDomain re-seed.

onShowMessage(msg: ReactNode): void

Called to display an informational or error message (e.g. "reached max zoom").

onFormatTimeToolTips(time: Date): ReactNode

Formats time for the cursor tooltips. Use your timezone library of choice — the component passes a raw Date.

// With luxon
onFormatTimeToolTips = (time) =>
  DateTime.fromJSDate(time).toFormat('yyyy-LL-dd HH:mm:ss');

onFormatTimeLegend(time: Date): string

Formats the x-axis tick. time is rounded to one of: millisecond, second, minute, hour, day, month, year. Return a different string per rounded level for readable ticks.

onFormatMetricLegend(value: number): string

Formats a metric amount shown at the top of the y-axis.

Imperative API

Three methods are exposed on the ref:

  • zoomIn() — zooms to the current cursor selection.
  • zoomOut() — pops the internal breadcrumb; if empty, expands by zoomOutFactor.
  • shiftTimeLine(delta) — animates the domain backward (>0) or forward (<0) by delta pixels.
const ref = useRef();
<TimeLine ref={ref} ... />
<button onClick={() => ref.current.zoomIn()}>+</button>

Styling

The library ships a single stylesheet (dist/timeline.css) whose rules are keyed on tl-<slot> class names. Every SVG element also receives any additional class you hand it via the classes prop — so your rules win via CSS specificity + source order.

/* my-theme.css */
.my-cursorArea       { fill: #4347fdff; stroke: #4347fdff; fill-opacity: 0.1; }
.my-cursorLeftHandle,
.my-cursorRightHandle { stroke: #4347fdff; }
.my-zoomIn           { fill: #4347fdff; }
.my-verticalAxisText { fill: #949494; }
.my-legend           { transform: translateX(-25px); }
import './my-theme.css';

<TimeLine
  classes={{
    cursorArea:       'my-cursorArea',
    cursorLeftHandle: 'my-cursorLeftHandle',
    cursorRightHandle:'my-cursorRightHandle',
    zoomIn:           'my-zoomIn',
    verticalAxisText: 'my-verticalAxisText',
    legend:           'my-legend',
    /* any tl-<slot> in timeline.css is overridable */
  }}
  ...
/>

Full list of slots: see src/timeline.css in the repo (or inspect a rendered timeline — every class starts with tl-). The test/src/App.jsx demo exercises the full set.

Migrating from v4

See MIGRATION-v5.md for a step-by-step guide. TL;DR:

  • moment objects → Date; moment.Durationnumber (ms).
  • domains: Domain[]domain: Domain | null.
  • onUpdateDomainsonDomainChange; onCustomRangeonTimeSpanChange.
  • onLoadHisto(intervalMs, start, end)onLoadHisto({intervalMs, start, end}).
  • onLoadDefaultDomain() now RETURNS the default (sync or Promise).
  • New required timeZone prop.
  • @material-ui/styles peer dep gone; style via the classes prop pointing at your own CSS class names.

Testing / Dev

git clone https://gitlab.com/TincaTibo/timeline.git
cd timeline
npm install
npm test          # Vitest regression suite (12 tests)
npm run build     # tsup -> dist/ (ESM + CJS + .d.ts + timeline.css)
npm run typecheck # tsc --noEmit

Demo app (Vite, no Docker required):

cd test
make start        # dev server on :5000 with HMR, aliased to ../src
make demo         # production build + `vite preview`

Editing any file under src/ hot-reloads the demo — no package rebuild.

Dependencies

  • React ≥ 16.8 (hooks).
  • d3-{scale,selection,drag,time}
  • luxon (runtime timezone engine — internal; host doesn't use it)
  • rc-tooltip
  • clsx
  • prop-types (dev-time only — types ship via .d.ts)

Removed in v5: @material-ui/styles, moment-timezone, lodash-es.