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

@chart-engine/core

v0.1.13

Published

High-performance financial charting engine (WASM) — adapter-based, exchange-neutral. Candles, indicators, drawings, pan/zoom, scalper UI.

Readme

@chart-engine/core

High-performance financial charting engine for the browser. Exchange-neutral and adapter-based: the chart renders on a canvas at 60 fps from a Rust WASM core, and never talks to a broker or exchange directly — it only ever sees an ExchangeAdapter.

Candles, 28+ indicators, drawing tools, pan/zoom, live streaming, and a scalper-friendly paper-trading surface — all in one dependency.

npm install @chart-engine/core

Features

  • Rust/WASM core — chart state, scales and indicator math run in WebAssembly; the JS layer only replays draw commands on a <canvas>.
  • Adapter-based data — wire any venue through an ExchangeAdapter, or start instantly with the built-in offline mock feed.
  • Chart types — candles, bars, line, area, Heikin-Ashi.
  • 28+ indicators — overlays (SMA, EMA, Bollinger, VWAP, Ichimoku, SuperTrend, …) and oscillators (RSI, MACD, Stochastic, ATR, ADX, CCI, …) auto-routed to a sub-pane.
  • Live streaming — upserted tick-to-candle aggregation (100+ updates/sec).
  • Tradingbuy / sell / closePosition; real orders via order-capable adapters, paper fills everywhere else.
  • Dark / light themes with custom accent color.

Quick start

<div id="chart" style="width:100%; height:600px"></div>
<script type="module">
  import { ChartEngine } from "@chart-engine/core";

  const engine = new ChartEngine({
    container: document.getElementById("chart"),
    initialAdapter: "mock",      // offline demo feed
    initialSymbol: "RELIANCE",
    initialTimeframe: "5m",
    accent: "#60a5fa",
  });
</script>

That's it. The chart mounts, loads history, subscribes to live ticks, and renders. Destroy it with engine.destroy() when you're done.

@chart-engine/core demo

Try the interactive playground: cd examples/basic && npm run dev — exchange switcher, symbol search, timeframes, indicators, and one-click buy/sell/close.

Built-in adapters

| key | venue | live | orders | notes | | ------ | --------- | ---- | ------ | -------------------------------------- | | mock | Synthetic | ✅ | paper | deterministic demo feed, no network |

The package also ships real-venue adapters (enabled via registerAdapter) so you can connect to a live exchange without writing any glue code.

API

new ChartEngine(options)

| option | type | description | | ------------------ | ------------------------ | ------------------------------------------------- | | container | HTMLElement \| string | Mount element or its id (required). | | initialAdapter | string | Adapter key (default: first registered). | | initialSymbol | string | Default symbol (default "RELIANCE"). | | initialTimeframe | string | Default timeframe (default "1m"). | | adapters | ExchangeAdapter[] | Extra adapters to register for this instance. | | theme | "dark" \| "light" | Default "dark". | | accent | string | Hex accent color (default "#60a5fa"). | | onReady | (engine) => void | Fires after WASM init and first data load. |

Methods

| method | description | | --------------------------- | ------------------------------------------------------------------ | | setExchange(key) | Switch data source and reload, re-subscribing to the live feed. | | setSymbol(sym) | Load a new symbol. | | setTimeframe(tf) | Switch timeframe. | | addIndicator(key, params?)| Add an indicator — see table below. | | removeIndicator(index) | Remove an indicator by index. | | setChartType(type) | "Candles", "Line", "Area", … | | setTheme(t) / setAccent(hex) | Appearance. | | fitAll() | Fit all visible candles. | | buy(qty?) / sell(qty?) | Place a market order (real or paper, per adapter). | | closePosition() | Close the current position. | | getPositions() | Current positions. | | currentSymbol() / currentTimeframe() / currentAdapter() | State getters. | | destroy() | Tear down canvas, event listeners and live subscription. |

Indicators

Overlays render on the main pane; oscillators auto-route to a sub-pane.

| key | params (defaults) | | --- | ----------------- | | sma, ema | { period: 20 } | | vwap, obv, pivot_points | — | | bollinger_bands | { period: 20, std_dev: 2 } | | keltner | { period: 14, multiplier: 2 } | | donchian | { period: 20 } | | super_trend | { period: 10, multiplier: 3 } | | parabolic_sar | { step: 0.02, max_step: 0.2 } | | ichimoku | { conversion: 9, base: 26, span_b: 52, displacement: 26 } | | ema_ribbon, ma_ribbon | — | | rsi (sub) | { period: 14 } | | atr (sub) | { period: 14 } | | macd (sub) | { fast: 12, slow: 26, signal: 9 } | | stochastic (sub) | { k_period: 14, d_period: 3, smooth: 3 } | | stoch_rsi (sub) | { rsi_period: 14, stoch_period: 14, k_smooth: 3, d_smooth: 3 } | | adx, aroon, vortex, trix, mfi, williams_r, cci (sub) | { period: 14…20 } | | momentum (sub) | { period: 10 } | | roc (sub) | { period: 12 } | | std_dev (sub) | { period: 14 } | | volume_profile | { bins: 24 } | | zig_zag | { pct: 5 } |

engine.addIndicator("sma", { period: 20 });        // overlay
engine.addIndicator("macd");                        // defaults: 12/26/9
engine.removeIndicator(0);

Bringing your own exchange

The engine only speaks to ExchangeAdapter. Implement it and register it:

import { ChartEngine, registerAdapter, type ExchangeAdapter } from "@chart-engine/core";

const myAdapter: ExchangeAdapter = {
  key: "myexchange",
  label: "My Exchange",
  capabilities: {
    historical: true,
    live: true,
    intradayTimeframes: ["1m", "5m"],
    orders: "real",
  },
  async searchSymbols(query) { /* ... */ },
  async fetchHistory({ symbol, timeframe, limit }) { /* candles */ },
  subscribe(symbol, timeframe, onCandle) {
    const ws = new WebSocket("wss://…");
    ws.onmessage = (e) => onCandle(parseTick(e.data));
    return { unsubscribe: () => ws.close() };
  },
  submitOrder(order) { /* place on your broker */ },
  cancelOrder(id) { /* ... */ },
  getPositions() { /* ... */ },
};

registerAdapter(myAdapter);

Built-in adapters self-register on import; registerAdapter/unregisterAdapter swap them at runtime. listAdapters() and getAdapter(key) inspect the registry.

Adapter contract

  • fetchHistory returns { time, open, high, low, close, volume } candles.
  • subscribe must call onCandle for both the in-progress candle and completed candles — the engine merges them via upsert_candle. Return an unsubscribe().
  • session.sessions is an array of [openMinute, closeMinute] market sessions that drive the trading-hours time axis; set session.aroundTheClock for 24/7 venues.
  • capabilities.orders: "real"submitOrder actually places; anything else fills paper on the last price.

Consuming in frameworks

The package is plain ESM with no framework dependency — it mounts on any <div>. Call destroy() on unmount to tear down the canvas, event listeners and live feed.

React

import { useEffect, useRef } from "react";
import { ChartEngine } from "@chart-engine/core";

export function Chart() {
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const engine = new ChartEngine({ container: ref.current!, initialAdapter: "mock" });
    return () => engine.destroy();
  }, []);
  return <div ref={ref} style={{ height: 500 }} />;
}

Vue

<script setup>
import { onMounted, onUnmounted, ref } from "vue";
import { ChartEngine } from "@chart-engine/core";
const el = ref();
let engine;
onMounted(() => { engine = new ChartEngine({ container: el.value }); });
onUnmounted(() => engine?.destroy());
</script>
<template><div ref="el" style="height:500px"></div></template>

Svelte

<script>
  import { onMount, onDestroy } from "svelte";
  import { ChartEngine } from "@chart-engine/core";
  let el, engine;
  onMount(() => { engine = new ChartEngine({ container: el }); });
  onDestroy(() => engine.destroy());
</script>
<div bind:this={el} style="height:500px"></div>

Platforms: Flutter, iOS, Android

@chart-engine/core renders to an HTML5 canvas, so it runs anywhere a browser (or WebView) does. It cannot run directly inside a Flutter/native app — the chart engine is Rust/WASM, not a Flutter widget or a native library. Three ways to consume it:

1. Browser apps (any web framework)

npm install @chart-engine/core — see the sections above. Vite, webpack, Rollup, esbuild, or a bare <script type="module"> from esm.sh all work with zero WASM configuration.

2. Flutter / React Native / native mobile (WebView)

Embed the engine's hosted web build in a WebView and drive it over postMessage. This is exactly what the hosted build is for — a standalone page (apps/web) that owns the chart and answers history/streaming via the ce:* protocol (ce:init, ce:ready, ce:request, ce:candles, ce:tick).

  • Flutter: webview_flutter or flutter_inappwebview, bridge via postMessage.
  • iOS: WKWebView + WKScriptMessageHandler.
  • Android: WebView + @JavascriptInterface.
  • React Native: react-native-webview.

The host page feeds candles and ticks over the message bridge; the chart stays inside the WebView and never touches your native code.

A hosted build is already deployed — point your WebView at https://chart-engine-ruby.vercel.app/index.html (live postMessage host demo at /example-host.html).

3. True native rendering (C FFI)

The same Rust core ships a C ABI (chart_engine_create, chart_engine_set_candles, chart_engine_render, …) that returns serialized draw commands. Compile the crates to a static library and bind them from Swift/Kotlin/Dart FFI, then replay the commands on a native canvas (Metal, UIKit, Skia). Heavier lift — intended for embedders that must avoid any WebView.

Embedding & passing your data

The hosted build (https://chart-engine-ruby.vercel.app/index.html) is a dumb rendering surfaceit owns no market data. Your host page (or Flutter/native WebView bridge) feeds it candles and ticks over window.postMessage.

Data flow

 host (parent window)                          chart (iframe)
 ─────────────────                             ─────────────────
  listen for ce:ready ◀───────────────────────  ce:ready {version}      (chart booted)
  send ce:init ──────────────────────────────▶  adopt symbol/timeframe/theme
  send ce:pong ◀──────────────────────────────  ce:ping                  (liveness)
  send ce:candles ◀───────────────────────────  ce:request-candles {requestId, symbol, timeframe, bars}
        │                                        (your history for that requestId)
  send ce:tick {price, time, volume} ────────▶  live ticks → in-progress candle
  send ce:candle {candle} ───────────────────▶  pre-aggregated candle (optional)

Minimal host page

<iframe id="chart" src="https://chart-engine-ruby.vercel.app/index.html"></iframe>
<script>
  const iframe = document.getElementById("chart");
  const ORIGIN = "https://chart-engine-ruby.vercel.app";

  // Listen first, before the iframe boots, so no ce:ready/ce:ping is dropped.
  window.addEventListener("message", (e) => {
    if (e.source !== iframe.contentWindow) return;
    const m = e.data;

    switch (m.type) {
      case "ce:ready":
        iframe.contentWindow.postMessage(
          { type: "ce:init", payload: { symbol: "RELIANCE", timeframe: "5m", theme: "dark" } },
          ORIGIN,
        );
        break;

      case "ce:ping":
        iframe.contentWindow.postMessage({ type: "ce:pong", payload: {} }, ORIGIN);
        break;

      case "ce:request-candles": {
        const { requestId, symbol, timeframe, bars } = m.payload;
        const candles = await myApi.candles(symbol, timeframe, bars); // your data source
        iframe.contentWindow.postMessage(
          { type: "ce:candles", payload: { requestId, symbol, timeframe, candles } },
          ORIGIN,
        );
        break;
      }

      case "ce:notify":
        // chart now shows symbol/timeframe — start/stop your live feed here
        break;
    }
  });

  // Push live prices as they arrive from your websocket/REST:
  function onTick(price, time) {
    iframe.contentWindow.postMessage(
      { type: "ce:tick", payload: { price, time, volume: 0 } },
      ORIGIN,
    );
  }
</script>

Candle shape

candles is an array of { time, open, high, low, close, volume } where time is epoch ms. Ticks use { price, time, volume? } (epoch ms or seconds).

Full message reference

Host → chart: ce:init, ce:pong, ce:candles, ce:tick, ce:candle, ce:set-symbol, ce:set-timeframe, ce:set-theme, ce:set-chart-type, ce:indicator, ce:search-results, ce:drawings.

Chart → host: ce:ready, ce:ping, ce:request-candles, ce:request-search, ce:request-drawings, ce:notify, ce:drawings.

Every message is { type, payload }. Wire contract in apps/shared/src/protocol.ts; a working host demo is live at https://chart-engine-ruby.vercel.app/example-host.html.

Performance notes

  • Ingest and paint are decoupled. The WASM core mutates candle state freely; rendering is gated by a rAF loop and needs_render().
  • Upsert, don't append. Live feeds merge candles, so 100+ updates/sec cost only a JSON stringify per bar.
  • DPR-capped canvas at 2× for mobile perf.

Development

npm install
npm run build          # vite lib build + tsc declarations
npm run typecheck
npm run test           # vitest
cd examples/basic && npm run dev   # interactive playground

License

MIT