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

@livo-build/charts

v0.2.9

Published

livo-charts — a lightweight, dependency-free canvas charting library (candlesticks/line/baseline/Heikin-Ashi, zoom/pan, crosshair, MA/RSI/MACD/Stochastic/ATR indicators, volume profile, drawing tools, and live Hyperliquid/Polymarket/Signal-Radar feeds) wi

Readme

@livo-build/charts (livo-charts)

Live demos + docs: charts.livo.build — interactive examples with copy-paste code, plus agent-friendly llms.txt / llms-full.txt.

A lightweight, dependency-free canvas charting library. The core renders to a <canvas> with zero runtime dependencies; a thin, self-styled React wrapper is shipped under a separate entry so non-React consumers never pull React into their bundle.

It ships a TradingView-style trading chart: candlesticks, a line, a two-tone baseline area or Heikin-Ashi candles, a volume panel, a volume-by-price profile, a crosshair with price + time axis labels, an OHLCV legend, independent X and Y zoom (+ pan, plus touch/pinch), and a toolbar for intervals, USD/ETH, price/market-cap, log scale and fullscreen. The architecture (a framework-agnostic Chart controller + a pure draw() pass) is built to keep growing — indicators, overlays, multiple panes — without touching consumers.

Install

npm i @livo-build/charts

react / react-dom are optional peers — only needed for the React entry.

React

import { PriceChart } from "@livo-build/charts/react";

<PriceChart
  swaps={trades}          // [{ t: unixSeconds, p: priceUsd, v: usdVolume }, …]
  ethUsd={1711.81}        // enables the USD↔ETH toggle
  supply={1_000_000_000}  // enables the Price↔MCap toggle (or tokenAddress to read it)
  symbol="PEPE"
  quote="WETH"
  height={420}
/>

swaps are aggregated into OHLC+volume candles client-side. The toolbar (1s 1m 5m 15m 1h 4h 1d, candle/line, Price/MCap, USD/ETH, flip, log, fullscreen) and the OHLCV legend are built in. The canvas is long-lived, so zoom/pan survive prop updates (streaming trades won't reset the user's view). The wrapper is styled inline — no CSS framework required.

PriceChart only aggregates the swaps you give it — it doesn't fetch more, so panning stops at the oldest trade. For infinite back-history, pass onLoadOlder (fires when the user pans/zooms within ~one viewport of the start) and prepend older trades to swaps; the right-anchored view keeps the position stable. Scope the toolbar to intervals your data covers with intervals={[["1m", 60], ["5m", 300]]} (a thin series bucketed at 1h collapses into a couple of candles), and react to interval changes with onIntervalChange. For a turnkey lazy-loading chart, use a feed (HyperliquidChart / connectFeed) instead.

The default visible span is initialBars × interval. To open on, say, the last 7 days at 1h candles, give it a week of data and defaultInterval={3600} + options={{ initialBars: 168 }} (168 × 1h = 7 days). The user can still zoom/scroll out from there.

Market cap = price × supply. Pass supply directly, or pass tokenAddress (+decimals) to read totalSupply() once via rpcUrl (default https://cloudflare-eth.com, cached). The Price/MCap toggle only appears when one of those is set — otherwise it's hidden (no dead button), and it falls back to price if the RPC read fails.

Live Hyperliquid chart

A turnkey, live trading chart for any Hyperliquid market — fetches the public candle feed (no API key), polls for updates (preserving the user's zoom/pan), and supports moving-average overlays:

import { HyperliquidChart } from "@livo-build/charts/react";

<HyperliquidChart
  coin="BTC"                 // or "ETH", or a builder-dex name like "xyz:TSLA"
  interval="15m"             // 1m 5m 15m 1h 4h 1d (toolbar lets the user switch)
  indicators={[{ type: "ema", period: 21 }, { type: "sma", period: 50 }]}
  refetchMs={15000}          // poll cadence; 0 = fetch once
  height={420}
/>

<HyperliquidChart> is realtime: live candles stream over WebSocket, and older history loads lazily as the user scrolls left (infinite back-scroll) — both with no API key. Zoom/pan survive every update.

Live Polymarket & Signal Radar charts

Two more turnkey, key-free live charts wrap the framework-agnostic Chart:

import { PolymarketChart, SignalsChart } from "@livo-build/charts/react";

// A prediction-market outcome (probabilities → a % y-axis), from the public CLOB.
<PolymarketChart tokenId={yesTokenId} bucketSeconds={3600} label="Yes" />

// A token tracked by the Livo signals engine ("Signal Radar") — real indexed history.
<SignalsChart token="PEPE" bucketSeconds={300} indicators={[{ type: "ema", period: 21 }]} />

Both are ChartFeeds under the hood — drive the core directly with connectFeed:

import { Chart, connectFeed, polymarketFeed, signalsFeed } from "@livo-build/charts";

connectFeed(new Chart(el), polymarketFeed({ tokenId, bucketSeconds: 3600 }), { interval: 3600 });
connectFeed(new Chart(el2), signalsFeed({ token: "PEPE", bucketSeconds: 300 }), { interval: 300 });
  • polymarketFeed — bucketed OHLC from the public Polymarket CLOB prices-history endpoint, polled for a building candle. Prices are probabilities in [0, 1] (no volume). fetchPolymarketPriceHistory is exported for custom fetching.
  • signalsFeed — real OHLC from the Signal Radar index: it queries /graphql (allSwaps for the pool) and buckets the swaps into candles at any interval, paging older swaps in as you scroll — so 1m / 5m candles go days deep (the index has no TTL), and 1h/4h/1d just zoom out in time. The live candle is polled from /data (current price). If /graphql is unreachable (e.g. cross-origin CORS) or you pass history: "spark", it falls back to the snapshot's spark. fetchSignalsSwaps / fetchSignalsMarket / sparkCandles are exported. (Cross-origin use needs /graphql CORS-enabled on the engine; same-origin always works.)

Indicators

PriceChart and HyperliquidChart accept indicators — overlays drawn on the price pane. Each is { type: "sma" | "ema" | "wma" | "vwap" | "bollinger", period, color?, source?, mult? } (source defaults to close; vwap is cumulative; bollinger draws a 3-line band at mult std-devs, default 2). Colors fall back to a built-in palette by position. The pure sma/ema/wma/vwap/bollingerBands/computeIndicator helpers are exported from the core (e.g. feed bollingerBands(values).{upper,mid,lower} as three overlays).

Oscillators (RSI / MACD / Stochastic / ATR) render in their own stacked sub-panes below the volume panel — pass oscillators:

<PriceChart
  swaps={trades}
  indicators={[{ type: "ema", period: 21 }]}   // on the price pane
  oscillators={[
    { type: "rsi", period: 14 },               // 0–100 band with 70/30 rails
    { type: "macd", fast: 12, slow: 26, signal: 9 },  // line + signal + histogram
    { type: "stoch", period: 14, dPeriod: 3, smooth: 3 }, // %K/%D, 80/20 rails
    { type: "atr", period: 14 },               // auto-scaled volatility (price units)
  ]}
/>

Each pane defaults to 84px (height to override). The pure rsi(values, period), macd(values, fast, slow, signal) → { macd, signal, hist }, stochastic(candles, kPeriod, dPeriod, smooth) → { k, d } and atr(candles, period) helpers are exported too.

Chart types: candle · line · baseline · Heikin-Ashi

chartType (low-level chart.setChartType(...), or the toolbar's ▮▯ / ╱ / ⎓ / HA buttons) switches the price series:

  • candle — classic OHLC candlesticks.
  • line — a close line with a soft gradient area fill.
  • baseline — a two-tone area around a reference price (green above, red below). Set the reference with baselinePrice (defaults to the first visible close); the core exposes chart.setBaseline(price).
  • heikin — Heikin-Ashi candles: smoothed OHLC that filter noise and make trends easier to read. Indicators/oscillators still read the raw closes. The pure heikinAshi(candles) transform is exported.

Volume profile (volume-by-price)

A horizontal histogram of volume per price level, drawn over the price pane (peaked at the Point of Control). Pass volumeProfile (or toggle VP in the toolbar); the core has chart.setVolumeProfile(...):

<PriceChart swaps={trades} volumeProfile={{ buckets: 24, width: 0.16 }} />

It's computed over the visible window, so it tracks zoom/pan. The pure volumeProfile(candles, buckets) → { buckets, maxVol, poc } helper is exported.

Fitting the container & axis styling

By default PriceChart / HyperliquidChart fit the container (fitContent) — candles spread across the full plot width instead of clustering at a capped slot. Set fitContent={false} for the tight, right-anchored look (the low-level Chart defaults to tight; the React wrappers default to fill).

The ticks are easy to style — every axis knob is a prop (and a chart.setAxis({…}) call):

<PriceChart
  swaps={trades}
  priceFormat={(v) => `$${v.toLocaleString()}`}  // y-axis labels
  timeFormat={(t) => new Date(t * 1000).toLocaleTimeString()}
  priceTicks={6}                                  // horizontal gridlines (default 5)
  timeTicks={8}                                   // x-axis labels (default 7)
  axisFont="11px Inter, sans-serif"               // label font (color = theme.axis)
/>

The default price formatter is range-aware: it keeps just enough decimals to tell adjacent ticks apart, so a narrow high-value axis (e.g. WETH 1.71K–1.75K) renders 1.7350K / 1.7430K instead of duplicate 1.73Ks. Tick text color comes from theme.axis.

Log scale & themes

The price axis can be logarithmic — equal vertical distance = equal % move, the right default for assets that span orders of magnitude. PriceChart/HyperliquidChart expose a Log toolbar toggle (or pass options={{ logScale: true }}); the core has chart.setLogScale(true). Log mode auto-falls back to linear if any visible low is ≤ 0.

Two built-in themes ship as DEFAULT_THEME (dark) and LIGHT_THEME, also addressable via THEME_PRESETS.dark / THEME_PRESETS.light. Pass either (or a partial override) as theme, or call chart.setTheme(LIGHT_THEME).

Drawing tools (trendlines & horizontal lines)

The toolbar arms a one-shot draw: trendline (drag), horizontal line (click), fib Fibonacci retracement (drag — levels 0/23.6/38.2/50/61.8/78.6/100% between the two prices), and rectangle (drag). Drawings are anchored in data space, so they stay pinned to their price/time across pan, zoom and live updates. In the default cursor mode, click a drawing to select it, drag to move it, and double-click it to delete it; clears all.

<PriceChart swaps={trades} onDrawingsChange={(d) => save(d)} drawings={restored} />

Drive it from the core too: chart.setDrawMode("trendline" | "hline" | "none"), getDrawings() / setDrawings() / removeDrawing(id) / clearDrawings(), and the onDrawingsChange callback. Pass hideDrawingTools to drop the buttons. The computeProjection(input) helper exposes the same pixel↔data mapping for custom tools.

Trade markers & annotations

Pin point annotations — trade fills, signals, news flags — to the price pane. Each marker is anchored in data space (time, optional price) so it tracks pan/zoom. With no price it pins to the candle at time (above the high for sells, below the low for buys). side drives the default color/glyph; override with color / shape ("triangle" | "arrow" | "circle" | "flag") / position.

<PriceChart
  swaps={trades}
  markers={fills.map((f) => ({ time: f.ts, side: f.isBuy ? "buy" : "sell", text: f.label }))}
/>
// or on the core: chart.setMarkers([{ time, price: 105, side: "sell", text: "TP", shape: "flag" }])

Comparison overlays (shape vs another asset)

Overlay a second series for visual shape comparison (e.g. token vs BTC). Each compare series is drawn on its own auto-fit scale — so assets of wildly different magnitudes line up by shape rather than being squashed by a shared axis — with a top-left legend showing its % change over the visible window.

<PriceChart swaps={trades} compare={[{ label: "BTC", candles: btcCandles }]} />
// or: chart.setCompare([{ label: "ETH", candles: ethCandles, color: "#26a69a" }])

(Multiple oscillator sub-panes already stack below volume; literal side-by-side panes were skipped as low-value.)

Realtime feeds + lazy history (any data source)

Connect a chart to a feed — a source that serves paged OHLCV history and live updates — and connectFeed handles the latest page, lazy older-history loading on left-scroll, and merging live candles (updating the in-progress bucket or appending):

import { Chart, connectFeed, hyperliquidFeed, HL_INTERVAL_SECONDS } from "@livo-build/charts";

const chart = new Chart(el, { height: 420 });
const conn = connectFeed(chart, hyperliquidFeed({ coin: "BTC", interval: "1m" }), {
  interval: HL_INTERVAL_SECONDS["1m"],
  pageSize: 500,
});
// …later
conn.disconnect();

Bring your own source by implementing ChartFeed:

const feed = {
  loadHistory: ({ interval, before, limit }) => fetchMyCandles(before, limit), // [] at the end
  subscribe: ({ interval }, onCandle) => mySocket.onCandle(onCandle),          // returns unsubscribe
};

The chart's right-anchored view means prepending history keeps the visible window stable. The controller prefetches the next page once the view's left edge comes within ~one viewport of the oldest loaded candle (see the exported needsHistory helper) — so zooming out keeps deepening the window with real data instead of stalling at the oldest bar. It advances one page per data length and stops when the feed runs dry.

Vanilla / framework-agnostic

import { Chart, buildOHLC } from "@livo-build/charts";

const chart = new Chart(containerEl, {
  height: 420,
  onCrosshair: (candle) => renderLegend(candle), // null when empty; last candle when idle
});
chart.setInterval(300).setChartType("candle");
chart.setCandles(buildOHLC(trades, 300, { denom: "ETH", ethUsd: 1711.81 }));

// streaming update — view (zoom/pan) is preserved
chart.setCandles(buildOHLC(moreTrades, 300));

chart.destroy();

Chart API

| Method | Description | | --- | --- | | new Chart(container, options?) | Creates a <canvas>, wires interaction, observes resize. | | setCandles(candles) | Replace the series (each candle has vol) and redraw. | | setInterval(seconds) | Bucket interval — drives time-axis label granularity. | | setChartType("candle" \| "line" \| "baseline" \| "heikin") | Switch series style. | | setShowVolume(on) | Show / hide the volume panel (the price pane reclaims the space). | | setVolumeProfile(config) | Show/configure (or hide with false) the volume-by-price histogram. | | setBaseline(price?) | Reference price for the baseline type (undefined = first visible close). | | setLogScale(on) | Toggle the logarithmic price axis (auto-falls back to linear if any low ≤ 0). | | setAxis({ fitContent, priceFormat, timeFormat, priceTicks, timeTicks, axisFont }) | Style the axes — only the provided keys change. | | setIndicators(indicators) | Set the moving-average overlays and redraw. | | setOscillators(oscillators) | Set the RSI / MACD sub-panes and redraw. | | setMarkers(markers) | Set point annotations (trade fills / signals / flags) on the price pane. | | setCompare(series) | Set comparison overlays (secondary assets, own auto-fit scale). | | setDrawMode(mode) | Arm a drawing tool ("trendline" / "hline") or "none". | | setDrawings / getDrawings / removeDrawing(id) / clearDrawings() | Manage drawings. | | setNeedHistory(cb) | Callback fired when the view nears the start of loaded data (lazy history). | | setHeight(px) / setTheme(partial) | Resize / merge theme overrides. | | resetView() | Reset zoom/pan (same as double-click). | | getViewState() / setViewState(partial) | Snapshot / restore the camera + mode (zoom, pan, yZoom, interval, type, log, volume) — persist across reloads. Offset/zoom re-clamp to the current data. | | setAriaLabel(label) | Update the canvas's accessible label (the live last-price suffix is appended automatically). | | toImage(type?, quality?) | Export the current canvas as a data-URL (PNG by default) for share cards / thumbnails. | | destroy() | Remove listeners, observer and the canvas. |

ChartOptions: height, theme, initialBars (120), minBars (20), maxBarWidth (18 — caps candle spacing so sparse series stay tight, right-anchored), volumeRatio (0.18), rightPad/bottomPad/topPad, onCrosshair, onCrosshairMove (richer crosshair: candle + index + pixel position + price, null on leave — drives synced crosshairs and position-aware tooltips), ariaLabel, keyboard (true), indicators, oscillators, markers, compare, drawings, showVolume (true), volumeProfile, baselinePrice, logScale (false), fitContent (false), maxBarWidth (18) / maxBodyWidth (40), priceFormat/timeFormat, priceTicks (5) / timeTicks (7), axisFont, and onNeedHistory.

Persisting a view — save on change, restore on mount:

const chart = new Chart(el, {
  // fires on every pan/zoom-affecting hover; debounce a save of the camera
  onCrosshairMove: () => localStorage.setItem("chartView", JSON.stringify(chart.getViewState())),
});
const saved = localStorage.getItem("chartView");
if (saved) chart.setViewState(JSON.parse(saved)); // safe even if the data length changed

Helpers

  • buildOHLC(points, interval, transform?) — bucket priced trades into OHLC+volume candles. transform = { denom, ethUsd, flip, supply } (supply → market cap).
  • transformPrice(p, transform?) — apply the transform to one price.
  • sma / ema / wma / vwap / bollingerBands / rsi / macd / stochastic / atr / volumeProfile / heikinAshi / computeIndicator — pure indicator + transform math (aligned to input, null until the window fills).
  • connectFeed(chart, feed, opts) — wire a ChartFeed (paged history + live stream) to a chart: latest page, lazy older history, live merge. Returns { disconnect }.
  • hyperliquidFeed({ coin, interval, testnet }) — a ready ChartFeed (REST history + WebSocket live). fetchHyperliquidCandles / fetchHlCandleWindow / mapHlCandles / HL_INTERVAL_SECONDS are exported for custom fetching.
  • polymarketFeed({ tokenId, bucketSeconds }) — a ChartFeed for a Polymarket outcome (CLOB price-history + polling). fetchPolymarketPriceHistory is exported.
  • signalsFeed({ token, bucketSeconds }) — a ChartFeed for a Signal Radar token: indexed allSwaps → OHLC (lazy older pages) + a /data-polled live candle, spark fallback. fetchSignalsSwaps / fetchSignalsMarket / sparkCandles are exported.
  • draw(input) — the pure render pass (exported for custom hosts/tests); returns the crosshair candle.
  • DEFAULT_THEME / LIGHT_THEME / THEME_PRESETS, formatValue, formatAxisValue (range-aware tick labels), formatVolume, formatTime.
  • slotWidth(plotW, count, maxBarWidth, fitContent?) — candle slot-width math (exported for custom hosts).
  • lttbIndices(values, threshold) — Largest-Triangle-Three-Buckets downsampling (returns the kept indices; shape-preserving). Used internally for huge zoomed-out line/area series.
  • computeProjection(input) / priceScale / timeScale / windowOf — the pixel↔data mapping the renderer and drawing tools share (returns { xOfTime, timeOfX, yOfPrice, priceOfY, … }).

Interaction

  • Scroll over the plot → zoom time; over the price axis → zoom price.
  • Scroll horizontally (trackpad swipe / shift+wheel) → pan through time; scrolling back into history lazy-loads older candles from the feed.
  • Drag the plot → pan; drag the time axis → zoom X; drag the price axis → zoom Y. Panning pins the oldest candle to the left edge (no dragging into empty space on the left); on a feed, or with PriceChart's onLoadOlder, reaching that edge streams in older history instead. You can scroll past the newest candle into the future — drag it up to halfway across to leave right-edge whitespace (so the current price isn't pinned to the edge).
  • Hover → crosshair + price/time axis labels + the OHLCV legend.
  • Double-click → reset zoom/pan (or delete a drawing when one is under the cursor).
  • Keyboard (when the canvas is focused; keyboard: true by default) → ←/→ pan, ↑/+ zoom in, ↓/- zoom out, 0/Home reset. The canvas is role="img" with a live aria-label (symbol + last price + candle count) for screen readers.
  • Touch → one finger pans (or zooms an axis from its gutter); two-finger pinch zooms time (horizontal spread) and price (vertical spread) together.
  • Drawing tools → arm trendline/h-line from the toolbar; select + drag to move, double-click to delete.

Design notes

  • No dependencies. The core is plain canvas 2D + ResizeObserver.
  • Core vs bindings. @livo-build/charts is vanilla (and tailwind-free); @livo-build/charts/react adds the toolbar/legend. Add more bindings the same way.
  • draw() is stateless. All zoom/pan/hover state lives in the Chart controller.
  • Render coalescing. High-frequency input (drag/hover/wheel) is batched into one requestAnimationFrame redraw, and indicator series are precomputed on data change (not per frame) — so panning a large series stays smooth.
  • Downsampling. Zoomed-out line/baseline charts with far more candles than pixels are drawn through an LTTB-downsampled point set (~one vertex per pixel) — shape-preserving, O(plotW) canvas work instead of O(visible). 100k candles still render in a few ms. The pure lttbIndices(values, threshold) is exported.

Develop

npm run typecheck   # tsc --noEmit
npm run test        # vitest (OHLC/volume aggregation + transforms, controller, render)
npm run build       # tsc → dist/ (index + react entries, with .d.ts)
npm run size        # build + gzip budget check (guards the dependency-free promise)

Published to npm via CI (.github/workflows/publish-charts.yml) on push to main when packages/charts/** changes. Bump version in package.json to release.