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

aurovie-charts

v0.15.0

Published

Framework-agnostic canvas charting engine — a TradingView-class financial chart (candles/volume, indicators, drawings, crosshair, pan/zoom) with a datafeed abstraction. React-free core; optional ./react binding.

Downloads

1,184

Readme

aurovie-charts

A framework-agnostic canvas charting engine — a TradingView-class financial chart (candles/volume, indicators, drawings, crosshair, pan/zoom) with a datafeed abstraction. The core is React-free; an optional ./react binding ships a full self-contained widget.

Why

Most financial-chart libraries either couple you to a framework or hide the engine behind a heavy widget. This one is a plain canvas engine you drive with data, plus an optional React component for when you want batteries included. No runtime dependencies in the core.

Install

npm i aurovie-charts
# react is a PEER dependency, needed ONLY for the ./react widget:
npm i react react-dom

Quick start

Vanilla (no framework)

import { Chart, DARK } from "aurovie-charts";

const host = document.getElementById("chart")!; // any HTMLElement
const chart = new Chart(host, { theme: DARK });

// Bar.time is UNIX SECONDS
chart.setData([
  { time: 1_700_000_000, open: 100, high: 104, low: 99, close: 103, volume: 1200 },
  // …
]);

React

import { TradingChart } from "aurovie-charts/react";
import type { DataFeed } from "aurovie-charts";

const feed: DataFeed = {
  async getBars(symbol, resolution) {
    const bars = await fetchBarsSomehow(symbol, resolution);
    return { bars }; // { bars: Bar[]; dataVersion?: string }
  },
  // optional realtime + search:
  // subscribe(symbol, resolution, onTick) { …; return unsubscribe; },
  // async searchSymbols(query) { return [{ symbol, description }]; },
};

export default function App() {
  return (
    <TradingChart
      datafeed={feed}
      symbol="DEMO"
      resolution="60"
      timeframes={[{ label: "1H", value: "60" }, { label: "1D", value: "1D" }]}
      height={600}
    />
  );
}

Core concepts

The DataFeed contract

A consumer implements a DataFeed — this is the integration surface, and the only required method is getBars:

interface DataFeed {
  getBars(symbol: string, resolution: Resolution): Promise<DataFeedResult>;
  subscribe?(symbol: string, resolution: Resolution, onTick: (bar: Bar) => void): () => void;
  searchSymbols?(query: string): Promise<{ symbol: string; description: string }[]>;
}

type DataFeedResult = { bars: Bar[]; dataVersion?: string };
type Bar = { time: number /* UNIX SECONDS */; open: number; high: number; low: number; close: number; volume?: number };

The engine never makes network calls — your DataFeed owns all fetching, auth, and polling.

Theming

Pass a Partial<Theme> over the built-in DARK (or LIGHT) default and override any field — e.g. map your app's design tokens onto it:

import { DARK } from "aurovie-charts";
new Chart(host, { theme: { ...DARK, up: "#12b886", down: "#e03131", background: "#000" } });

THEMES is a map of named presets and THEME_NAMES lists them (used by the React widget's theme picker).

Indicators

The React widget accepts a list of built-in indicator ids via its indicators prop:

ma50 · ma200 · ema21 · vwap · boll · donch · rsi · macd · atr · stoch · obv · cci · roc

API (core)

  • Chart — new Chart(host, opts?); setData, update, setIndicators, setScripts, setTool, setScaleMode, getDrawings/setDrawings, …
  • Themes: DARK, LIGHT, THEMES, THEME_NAMES
  • Script overlays: parseScriptDraw, parseScriptDrawJson, scriptColor
  • Trade arithmetic: deriveTicketRisk, bracketCoherent, EMPTY_ORDER — React-free, so the ticket and the chart cannot disagree about one calculation
  • Types: Bar, SeriesType, Resolution, Theme, DataFeed, DataFeedResult, ChartOptions, IndicatorInstance, LegendValue, PriceLine, ChartMarker, ScaleMode, Tool, Drawing, ScriptDraw, ScriptPlot, ScriptRender, ScriptColor, ScriptPlotStyle, TicketOrder, TicketRisk

API (aurovie-charts/react)

  • TradingChart — the full self-contained widget (toolbar + drawing rail + legend) wired to a DataFeed
  • TradeTicket — a controlled order ticket in the chart's own theme (see below)
  • ChartWorkspace — the chart-and-panel layout, docked or stacked on container width
  • Types: TradingChartProps, TimeframeOption, RangePreset, ChartSettingGroup, TradeTicketProps, TicketOrder

One chart, two depths

A chart in a page is read by two people at different moments: one glancing at what a thing is worth, one working with candles, indicators and orders. mode serves both from one widget.

const [mode, setMode] = useState<"simple" | "advanced">("simple");

<TradingChart datafeed={feed} symbol="DEMO" mode={mode} />

"simple" is a baseline series with an endpoint dot, a price axis and no time axis, no volume pane, no toolbar, no drawing rail and no legend. What it does not remove is the chart — zoom, pan, pinch and the crosshair are the widget's and they stay in both modes, because a glance chart that cannot be interrogated is a picture of a chart.

It is a layer of defaults, not an override, so any prop passed explicitly still wins:

// a glance chart that happens to want its volume pane
<TradingChart datafeed={feed} symbol="DEMO" mode="simple" volume />

"advanced" is the default and implies nothing, so a host that never passes mode behaves exactly as it did before this existed.

Own the switch and remember the choice. A chart that opens advanced because some flag was set makes its page feel like a different product from the one next door — having the drawing rail available is not the same as asking to start inside it.

Driving host chrome from the widget

The widget owns its toolbar and legend, but a host usually has chrome of its own to keep in sync. Three props open that up without giving up the batteries-included widget:

<TradingChart
  datafeed={feed}
  symbol="DEMO"
  // mirror the crosshair into your own price header
  onCrosshair={(bar) => setScrubbed(bar)}
  // draw series YOU computed (a model forecast, an equity curve)
  scripts={forecast ? [forecast] : []}
  // gate indicators behind a plan; locked entries stay listed, with a lock
  lockedIndicators={plan === "free" ? ["rsi", "macd", "boll"] : []}
  onLockedIndicator={(id) => openUpgrade(id)}
/>

scripts takes the same ScriptRender shape the script editor produces (parseScriptDraw builds one). Host series are the host's to invalidate — the widget clears the editor's scripts when the symbol or interval changes, but never yours, since only you know whether you have recomputed.

On a phone

The widget measures its own box and switches to a compact layout under 560px — one scrolling row of controls instead of a wrapping toolbar, 32px touch targets, and everything else on one bottom sheet reached from the toolbar's ⋯ or the bottom bar's ⚙:

<TradingChart
  datafeed={feed}
  symbol="AAPL"
  // "auto" (default) measures the CONTAINER, not the viewport. Force it either way if you know
  // better than the measurement — an app shell that is always a phone, a kiosk that never is.
  compact="auto"
  // Give it real height: the reclaimed chrome goes to the plot, which is a flex child.
  height="100dvh"
/>

Nothing is removed in compact — the drawing tools, compare, replay, the script editor, the scale switches and every display toggle all live in the sheet. Because the sheet is reachable from the bottom bar too, toolbar={false} still leaves every control available.

The bottom bar: ranges and host settings

Range presets and the scale switches live together in the chart's bottom bar — they are the same kind of control (how this chart is drawn, not what is drawn on it). A host's own chart settings join them there as declared groups, so the widget draws them in its own button vocabulary rather than the host restating the chart's styling in its design system:

<TradingChart
  datafeed={feed}
  symbol="CORBL"
  ranges={[
    { label: "1M", days: 30, note: "Daily bars" },
    // a thunk for windows whose length changes daily — resolved on the CLICK
    { label: "YTD", days: () => ytdDays(), title: "Year to date", note: "Daily bars" },
    { label: "ALL", days: null, note: "Weekly bars" },
  ]}
  range={range}                       // controlled, so a persisted choice lights its own pill
  onRangeChange={(r) => applyRange(r)} // the widget moves the VIEWPORT; only you know the resolution
  settings={[
    {
      id: "prices",
      label: "Prices",
      value: prices,
      onChange: setPrices,
      options: [
        { value: "market", label: "Market", title: "As traded — the archive, unmodified" },
        { value: "adjusted", label: "Adjusted", title: "Back-adjusted for bonuses and splits", note: "1 event" },
      ],
    },
  ]}
/>

An option's note prints only while that option is active, so it can never describe a basis the chart is not drawing. Past four options a group collapses to a menu (as forces either). footer still takes arbitrary nodes, for anything that is not a choice between options.

Trading beside the chart

TradeTicket is a controlled, purely presentational order ticket wearing the same theme as the plot. It holds no state, makes no request, and knows nothing about your broker — you own the state, the pre-trade gate and the submit:

import { ChartWorkspace, TradeTicket } from "aurovie-charts/react";
import { deriveTicketRisk, EMPTY_ORDER } from "aurovie-charts";

const [order, setOrder] = useState({ ...EMPTY_ORDER, qty: 100 });
const risk = deriveTicketRisk(order, { quote, account });

<ChartWorkspace
  aside={
    <TradeTicket
      order={order}
      onChange={(patch) => setOrder((o) => ({ ...o, ...patch }))}
      symbol="AAPL"
      quote={{ last: 305.13, bid: 305.11, ask: 305.15, changePct: -0.15 }}
      account={{ equity: 984_786, buyingPower: 3_933_909 }}
      checks={gate.map((c) => ({ label: c.name, ok: c.passed, detail: c.detail }))}
      onSubmit={place}
      currency="USD"
    />
  }
>
  <TradingChart datafeed={feed} symbol="AAPL" plan={{ side: "long", ...risk }} />
</ChartWorkspace>

Every figure whose inputs are missing is omitted, not printed as a zero — risk computed against an absent stop is a fabricated number, and it would look like the most confident one on the ticket. deriveTicketRisk and bracketCoherent are exported from the React-free core, so the same arithmetic can gate your submit and draw your plan without two implementations drifting apart.

Instrument header, ranges, and sessions

<TradingChart
  datafeed={feed}
  symbol="GMFBS"
  header={{
    name: "Ganapati Laghubitta Bittiya Sanstha Limited",
    sector: "Microfinance",
    stats: [{ label: "52W", value: "1,130.5-1,688.0" }, { label: "Vol", value: "24" }],
    price: { value: "1,160.00", change: "▲ 27.00 (2.38%)", direction: "up" },
  }}
  ranges={[{ label: "6M", days: 180 }, { label: "1Y", days: 365 }, { label: "All", days: null }]}
  session={{ openMin: 11 * 60, closeMin: 15 * 60, days: [0, 1, 2, 3, 4], utc: true }}
/>

Every header field is optional — a field you cannot fill is omitted, never rendered as a dash or a zero, which would state something false about the instrument. priceSlot replaces the price block entirely when your ticker animates.

session drives the intraday out-of-hours shading. It defaults to US_EQUITIES_SESSION, so set it for any other venue or the shading marks the wrong bars. Use utc: true when your bar times are exchange wall-clock stamped as UTC — otherwise the shading is computed in the reader's timezone.

Design system

The React layer resolves through one module, react/ui.ts: a 4px spacing grid, a radius ramp keyed to the size of the thing, a type scale, four control heights, one elevation set, and a single scoped stylesheet carrying every interactive state.

The rule it enforces:

Classes own colour, state and elevation. Inline styles own layout only.

That is not a style preference. An inline background outranks a class's :hover in the cascade, so a control that paints itself inline can never light up under the pointer — which is why the widget previously had no hover or pressed states anywhere. Theme reaches the stylesheet as CSS custom properties stamped on the widget root, so one static sheet serves every theme and every host override.

Two colour helpers are worth knowing about if you pass a themeOverride:

import { readable, contrast } from "aurovie-charts/react";

contrast("#ebae3d", "#ffffff"); // 1.97 — the default gold, unreadable as text on white
readable("#ebae3d", "#ffffff"); // a darkened gold that clears 4.5:1

The widget applies this automatically: --ac-accent keeps your exact hue for fills and strokes, while --ac-accent-ink is the same colour moved until it is legible as text. Point theme.line at whatever brand colour you like — selected controls stay readable on light and dark alike.

Standalone build (no ES modules)

For hosts that cannot import an ES module — a React Native WebView, a <script> tag, a page that inlines its whole bundle — aurovie-charts/standalone is the React-free core as one self-contained IIFE that defines window.AurovieCharts:

<script src="node_modules/aurovie-charts/dist/aurovie-charts.standalone.global.js"></script>
<script>
  const chart = new AurovieCharts.Chart(document.getElementById("chart"), { theme: AurovieCharts.DARK });
  chart.setData(bars);
</script>

Examples

See examples/ — a zero-framework vanilla example and a react example.

License

MIT