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

@razedotbot/charts

v0.6.0

Published

Dependency-free financial and dashboard charts with a documented TradingView-shaped compatibility subset, typed native grammar, and React adapter.

Readme

Raze Charts

Raze Charts is a dependency-free TypeScript toolkit for product-grade charts. It combines a realtime financial widget, a small renderer-neutral chart grammar, and a thin React adapter without locking product UI behind a proprietary shell.

The priorities are deliberate: a clear API, predictable lifecycle, excellent defaults, honest compatibility, and escape hatches that remain typed.

Raze Charts is not affiliated with TradingView. It is an independent, clean-room implementation of a documented subset of the TradingView widget and datafeed interfaces. TradingView is a trademark of TradingView, Inc.

Choose a surface

| Import | Use it for | Contract | | --- | --- | --- | | @razedotbot/charts | Realtime financial charts and low-level financial building blocks | TradingView-shaped compatibility subset plus Raze-native exports | | @razedotbot/charts/chart | Framework-neutral product and dashboard charts | Native typed grammar, SVG string rendering, SVG/Canvas DOM mounting | | @razedotbot/charts/react | React product charts or an incremental Recharts migration | Thin adapter over /chart; React is an optional peer dependency | | @razedotbot/charts/studies | Indicator math and study definitions without the widget (servers, workers, screeners) | Pure kernels (sma, ema, rsi, macd, bollinger, vwap, …), StudyRegistry, BUILTIN_STUDIES, and the StudyDefinition contract |

Those are the only public package subpaths today. Imports from src/** or dist/** are implementation details and are not a compatibility contract.

For a precise feature-by-feature view, see the capability matrix. For internals and extension points, see architecture.

Product principles

  • Native first. The typed chart definition and data source own behavior; compatibility layers translate into those contracts.
  • No silent compatibility. A supported component works; a known unsupported Recharts prop such as Line fill fails with guidance.
  • One explicit lifecycle. Mounted charts update in place and expose cleanup; stale async work cannot regain ownership after a target change or teardown.
  • One semantic scene. SVG and Canvas consume the same compiled chart, with renderer parity tested rather than assumed.
  • Measured quality. Accessibility, package formats, visual output, dense data, and bundle size have documented checks and honest boundaries.

Install

npm install @razedotbot/charts

Node 18 or newer is required for development and server-side SVG generation. React 17 or newer is required only when importing the React entrypoint. There are no runtime dependencies. The published declaration surface requires TypeScript 5.0 or newer.

Native charts: recommended for new product UI

Data keys are inferred from the datum type. A misspelled key is a TypeScript error, and accessors can be functions when a key is not enough.

import { defineChart, line, mountChart } from "@razedotbot/charts/chart";

type PricePoint = {
  time: Date;
  close: number;
};

const prices: PricePoint[] = [
  { time: new Date("2026-09-01T00:00:00Z"), close: 42.1 },
  { time: new Date("2026-09-02T00:00:00Z"), close: 44.8 },
];

const definition = defineChart({
  marks: [
    line(prices, {
      x: "time",
      y: "close",
      name: "Close",
      stroke: "#66d89e",
    }),
  ],
  scales: { x: { type: "time" } },
  tooltip: true,
  legend: true,
  ariaLabel: "Daily close price",
  ariaDescription: "Close price for 1 and 2 September 2026.",
});

const chart = mountChart(document.querySelector("#chart")!, definition, {
  renderer: "svg", // or "canvas"
  height: 320,
});

// Preserve the host and interaction lifecycle while data or options change.
chart.update(definition, { height: 360 });
chart.destroy();

Wheel zoom, pan, and brush stay within interaction.zoom: { minSpan, maxSpan } and interaction.panBounds ("data" by default); onTooltip/onSelect payloads carry data-space x/y, datum, and index. See the capability matrix for the defaults.

Built-in marks are line, area, bar, point, ruleY, pie, radar, and heatmap. Scales are linear, band, time, and log. The compiler produces a renderer-neutral scene that can be inspected with getScene() or rendered with renderChartSvg, svgFromCompiled, and paintChartCanvas.

Omit scales.x to infer it from the data; once an X scale object is present, its type is required so configuration cannot silently mean something else. Built-in marks expose mark-specific option types, and dynamic JavaScript input is checked against the same boundary with stable ChartCompileError codes. Pie and heatmap are standalone compositions. Multiple radar layers may be overlaid when they share the same axes; Cartesian scales cannot be mixed into a polar chart.

Every mark is a series with a stable id (its id option, else mark-<index>). Legend clicks and hiddenSeries use that id, and a hidden series stays in the legend, dimmed, so it can be shown again. Series that share a name get numbered legend rows (Revenue (2)) unless they also share a colour, which groups them into one row. scales.y.tickFormat formats tooltips, value chips and rule labels as well as ticks.

Dense line and area geometry is reduced by a pixel-aware extrema envelope by default. Tune it with performance.maxRenderedPoints, opt out with performance.decimation: "none", and inspect CompiledChart.diagnostics. While decimation is enabled, the configured maximum is a hard per-series cap on source-derived path samples (minimum 1; area closure vertices are extra). The compiler still scans source rows, so this bounds render complexity without pretending input processing is free.

Axes are measured at compile time: tick decimals follow the tick step, large values switch to compact notation (1.2T), the value axis widens to fit its labels and last-value chips, category labels thin or rotate instead of overlapping (scales.x.labels: { rotate, maxWidth, interval }), and time scales tick on calendar boundaries from seconds to decades. Heatmap values are plain numbers unless the mark sets valueFormat ("percent", "signed", "signed-percent", or a function).

Product-specific layers can use the typed defineMarkPlugin + customMark extension point without forking the compiler. Plugins receive isolated, read-only scale/theme snapshots and must return validated, discriminated scene geometry. The plugin contract is documented in architecture.

Financial widget

The root entrypoint is for candlesticks, live bars, financial studies, drawings, marks, and TradingView-style datafeeds.

import {
  widget,
  type IBasicDataFeed,
  type ResolutionString,
} from "@razedotbot/charts";

declare const datafeed: IBasicDataFeed;

const financialChart = new widget({
  container: document.getElementById("chart")!,
  symbol: "MYTOKEN",
  interval: "1" as ResolutionString,
  datafeed,
  autosize: true,
  theme: "dark",
  raze: {
    chart_types: ["candles", "line"],
    compact_breakpoint: 520,
    aria_label: "MYTOKEN price chart",
    aria_description: "One-minute candles quoted in USD.",
  },
});

financialChart.onChartReady(() => {
  void financialChart.activeChart().createStudy(
    "EMA",
    false,
    false,
    { length: 21 },
  );
});

// Required when the host unmounts.
financialChart.remove();

Trading overlays

Orders and positions are first-class broker primitives rather than drawings. They stay above the price series, participate in auto-scale, support mouse and touch dragging, and can be nudged one minimum tick with Up/Down while selected. The fluent order/position adapters mirror the TradingView integration style; createBracketOrder links entry, stop-loss, and take-profit with live risk/reward shading and callbacks.

financialChart.onChartReady(async () => {
  const chart = financialChart.activeChart();
  const bracket = await chart.createBracketOrder({
    side: "buy",
    entryPrice: 101.5,
    stopLossPrice: 98,
    takeProfitPrice: 108.5,
    quantity: 2,
    currency: "USD",
    onChange: (snapshot, event) => {
      // `moving` is a transient preview. Persist only the final price.
      if (event.type === "moved") {
        sendOrderAmendment(event.line.id, event.line.price);
      }
      updateRiskPreview(snapshot.riskRewardRatio);
    },
    onCancel: (snapshot) => cancelOrderGroup(snapshot.id),
  });

  bracket.stopLoss
    ?.setLineColor("#ef5350")
    .onMove((line) => console.log("new stop", line.getPrice()));

  const limit = await chart.createOrderLine({
    side: "sell",
    price: 112,
    quantity: 1,
    text: "Reduce",
  });
  limit.onCancel(() => cancelOrder(limit.id));
});

trading_event subscriptions receive a detached line snapshot plus the event type. Trading overlays are deliberately excluded from widget.save() because broker state must be rehydrated from the execution backend, not a chart layout.

For a standalone browser bundle, dist/charting_library.standalone.js assigns window.TradingView.widget:

<script src="/static/charting_library.standalone.js"></script>
<script>
  const chart = new TradingView.widget({ /* widget options */ });
</script>

For new integrations, a Promise-first data source avoids callback plumbing and adapts to the widget protocol:

import {
  createDatafeed,
  defineDataSource,
  type ResolutionString,
} from "@razedotbot/charts";

const source = defineDataSource({
  async resolveSymbol(symbol) {
    return {
      name: symbol,
      ticker: symbol,
      description: symbol,
      type: "crypto",
      session: "24x7",
      timezone: "Etc/UTC",
      exchange: "Raze",
      listed_exchange: "Raze",
      format: "price",
      minmov: 1,
      pricescale: 100,
      has_intraday: true,
      supported_resolutions: ["1", "5", "15"] as ResolutionString[],
    };
  },
  async getBars({ symbol, resolution, from, to }) {
    const query = new URLSearchParams({
      symbol,
      resolution: String(resolution),
      from: String(from),
      to: String(to),
    });
    const response = await fetch(`/api/bars?${query}`);
    if (!response.ok) throw new Error(`History failed: ${response.status}`);
    return response.json();
  },
});

const datafeed = createDatafeed(source, {
  supportedResolutions: ["1", "5", "15"] as ResolutionString[],
});

subscribeBars additionally receives an AbortSignal and may resolve to a cleanup function, making async realtime setup safe. Existing IBasicDataFeed implementations remain supported directly.

The data path handles initial history, left-scroll pagination, live updates, marks, symbol/interval changes, and stale async callbacks. TimeIndex maps real timestamps to logical bars, so weekends and missing sessions do not create phantom candles for drawings or marks. See performance and data correctness.

Financial customization

Chrome is data-driven through favorites, feature flags, and raze options:

new widget({
  ...requiredWidgetOptions, // container, symbol, interval, datafeed
  favorites: { intervals: ["1", "5", "15"] as ResolutionString[] },
  disabled_features: ["scale_bar"],
  raze: {
    sidebar: [
      "cursor",
      "trend_line",
      "horizontal_line",
      "separator",
      "indicators",
      "fit",
      "screenshot",
    ],
    indicator_presets: [
      { name: "EMA", length: 9 },
      { name: "RSI", length: 14 },
    ],
    custom_studies: [
      {
        name: "MOM",
        pane: "pane",
        defaults: { length: 10, color: "#8ecae6" },
        levels: [{ value: 0, dashed: true, axisLabel: true }],
        compute: (bars, { length }) =>
          bars.map((bar, index) =>
            index < length ? null : bar.close - bars[index - length]!.close,
          ),
      },
    ],
  },
});

Custom studies use the public full-array compute contract. Built-in EMA, SMA, and RSI update incrementally for an appended or replaced live bar; backfills and custom studies recompute. This distinction matters for high-rate feeds and is intentionally documented rather than hidden.

For typed plugins, defineIndicator() from @razedotbot/charts/studies adds a validated input schema (int, source, select, …), plot/fill/level descriptors, a read-only compute context (symbol info, resolution, timezone, visible range) and incremental init()/update() steps that run once per live tick. Invalid inputs reject createStudy() with a StudyInputError. See docs/indicators.md. The legend, objects tree and Indicators menu label a study from its definition: MOM 10 above, EMA 9, BB 20 2, MACD 12 26 9 or VWAP for the built-ins. Set shortTitle or formatLabel(inputs) on the definition to change it.

Price formatting

One formatter controls the price axis, last-price tag, OHLC legend, crosshair, and shape price labels. A TradingView-shaped factory takes precedence; return null to fall through to the Raze-native formatter and then the built-in pricescale formatter.

declare function formatTokenPrice(value: number, pricescale?: number): string;

new widget({
  ...requiredWidgetOptions, // container, symbol, interval, datafeed
  custom_formatters: {
    priceFormatterFactory: (symbolInfo) =>
      symbolInfo ? { format: (value) => formatTokenPrice(value) } : null,
  },
  raze: {
    format_price: (value, pricescale) =>
      formatTokenPrice(value, pricescale),
  },
});

Percent-scale ticks keep percent notation. Overlay studies may still own their legend value through StudyDefinition.formatValue.

Time zones and time labels

timezone sets the zone of the time-axis labels, the crosshair time and the session breaks: an IANA zone, a fixed offset such as "+05:30", "exchange" for the symbol's timezone, or an id from custom_timezones. Labels are DST-correct and calendar-aligned, and their density follows the pixels per bar, so gapped equity sessions stay readable. Daily and coarser bars keep their trading date in every zone.

const tzChart = new widget({
  ...requiredWidgetOptions, // container, symbol, interval, datafeed
  timezone: "America/New_York",
  custom_timezones: [{ id: "desk", alias: "Europe/London", title: "Trading desk" }],
  custom_formatters: {
    // `date` carries the local time in its UTC fields; null keeps the default label.
    tickMarkFormatter: (date, type) =>
      type === "Year" ? `'${String(date.getUTCFullYear()).slice(2)}` : null,
  },
});

tzChart.onChartReady(() => {
  const chart = tzChart.activeChart();
  chart.onTimezoneChanged().subscribe(null, (zone) => console.log("timezone", zone));
  chart.setTimezone("Asia/Tokyo"); // throws a RangeError for an unknown zone
});

dateFormatter and timeFormatter replace the two halves of the crosshair time label the same way. An unknown zone in the options or in symbolInfo.timezone warns once and shows UTC.

The root also exports DataManager, TimeIndex, ChartEngine, ChartRenderer, ShapeStore, TradingStore, StudyStore, indicator math, formatting and resolution helpers, defineDataSource / createDatafeed, and the default UI chrome. These pieces are useful for a custom financial shell, but currently share the widget's mutable ChartContext; they are not separate package subpaths. Build that context with createChartContext(), and change the viewport, price scale and series style through its reason-tagged setters. The exception is indicator math: import it from @razedotbot/charts/studies to compute studies without bundling the widget.

import { ema, StudyRegistry, type StudyDefinition } from "@razedotbot/charts/studies";

const spread: StudyDefinition = {
  name: "Spread",
  pane: "pane",
  compute: (bars) => bars.map((bar) => bar.high - bar.low),
};
const registry = new StudyRegistry(); // built-ins plus anything you register
registry.register(spread);
const fast = ema([10, 11, 12, 13], 3); // [null, null, 11, 12]

The same StudyDefinition works in the widget through raze.custom_studies. The /studies bundle is separate from the root widget, so registering on a StudyRegistry created there does not change a mounted widget.

React

For the native API, <Chart> is a lifecycle adapter around a ChartDefinition. It mounts once and forwards new definitions, dimensions, renderer choices, and accessibility text through update().

For Recharts-shaped JSX, use the typed factory in new code:

import { createChartComponents } from "@razedotbot/charts/react";

type Sale = { month: string; revenue: number };

const { LineChart, Line, XAxis, CartesianGrid, Tooltip, Legend } =
  createChartComponents<Sale>({ xKey: "month", valueKey: "revenue" });

export function RevenueChart({ data }: { data: Sale[] }) {
  return (
    <LineChart
      data={data}
      height={320}
      ariaLabel="Monthly revenue"
      ariaDescription="Revenue from January through March."
    >
      <CartesianGrid />
      <XAxis dataKey="month" />
      <Line dataKey="revenue" name="Revenue" stroke="#66d89e" />
      <Tooltip />
      <Legend />
    </LineChart>
  );
}

Tooltip and Legend are functional configuration toggles consumed by the parent chart. They are not customizable overlay components. Brush windows the native viewport through startIndex/endIndex (or a time domain) and throws on unknown Recharts brush props; it is never silently ignored. The adapter is a migration convenience, not a drop-in implementation of the full Recharts API. See the Recharts migration matrix.

Each series component accepts only the options it implements, both in TypeScript and at runtime. ResponsiveContainer measures its box and injects numeric dimensions into its one child: a chart, your own wrapper component that forwards width/height, or a render function ({ width, height }) => …. Set visual size through chart props or the container, not Chart.style.width / height. onReady exposes detached, deeply frozen scene snapshots plus setViewport/getViewport, while React retains lifecycle and teardown ownership.

Re-rendering is cheap: inline callbacks, interaction={{ zoom: true }} and viewport literals never recompile, and Recharts-shaped charts memoize on the structure of their JSX children, so only a real data or prop change repaints (React update flow). Charts with the same syncId (or viewportGroup from createViewportGroup()) pan and zoom together; they share the X window, not the tooltip position (synchronized charts).

The React entry ships with a "use client" directive, so Next.js App Router Server Components can render <LineChart> without a client wrapper file of your own; pass serializable props from the server and keep callbacks in client components (details).

Design and accessibility

Dark and light themes ship with cohesive pane, grid, axis, tooltip, status, and series colors. Every native chart also accepts a partial theme, so a product can own its visual language without replacing the renderer. Default series palettes stay distinguishable under colour-vision deficiencies, and COLORBLIND_CHART_THEME / COLORBLIND_LIGHT_CHART_THEME switch up/down and heatmap colours from green/red to blue/orange.

Good visual defaults do not make every integration accessible automatically. Supply a specific ariaLabel, add ariaDescription when the trend needs context, preserve keyboard focus styles, and offer a table or textual summary when users need exact values. The implemented behavior and integration checklist live in the accessibility guide. Raze Charts does not claim a blanket WCAG conformance certification.

On phones and narrow viewports the widget's menus open as bottom sheets with touch-sized rows. The financial widget's chrome also works under a strict Content Security Policy: its styles need no 'unsafe-inline', and library markup goes only through a raze-charts Trusted Types policy. The /chart SVG renderer still needs 'unsafe-inline' for style attributes. See UI kit, CSP and localization.

Compatibility and migration

  • Capability matrix — what each entrypoint supports.
  • Migration guide — TradingView and Recharts mappings, differences, and unsupported surfaces.
  • Architecture — data flow, lifecycle, renderers, and custom mark plugins.
  • Performance — reproducible benchmarks, budgets, and large-data guidance.
  • Accessibility — current semantics and host-app responsibilities.

Development

npm ci
npm run quality
npm run test:visual

npm run quality runs strict source/API type checks, builds the distributable artifacts, runs every discovered tests/*.mjs suite, and exercises the widget, dashboard compiler and edge cases, SVG/Canvas color parity, React 17/18/19 contracts, data races, time indexing, studies, declaration watch mode, the packed ESM/CJS/NodeNext package contract, documentation, bundle budgets for published artifacts and tree-shaken consumer scenarios, and compiler performance. Visual tests use Playwright Chromium snapshots and remain a separate platform-specific gate.

npm run check:docs type-checks every ts/tsx fence in this README and docs/ against the built declarations, with both Bundler and NodeNext resolution. A fragment can declare hidden setup with <!-- prelude: financial --> (see scripts/check-doc-snippets.mjs) or opt out with <!-- no-check: reason -->.

Use npm run typecheck as the faster type-only feedback loop while editing.

Open examples/index.html, examples/dashboard.html, or examples/visual.html through a local HTTP server after building:

python -m http.server 8799

Contributions are welcome. Start with CONTRIBUTING.md.

License

MIT — see LICENSE.