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

@stacklatte/chart-core

v1.1.2

Published

High-performance React Native trading chart — GPU-accelerated via Skia. Candlesticks, indicators, zoom/pan, live streaming, dark/light themes.

Readme

@stacklatte/chart-core

High-performance React Native trading charts — GPU-accelerated via React Native Skia. Works on iOS, Android, and web.

npm License: MIT


Install

npm install @stacklatte/chart-core @shopify/react-native-skia react-native-gesture-handler

Follow the setup guides for react-native-skia and react-native-gesture-handler.


Peer dependencies

| Package | Version | |---|---| | react | >=19.0.0 | | react-native | >=0.79.0 | | @shopify/react-native-skia | >=2.0.0 | | react-native-gesture-handler | >=2.0.0 |


Candlestick chart

Drop in OHLC candle data and get a GPU-rendered, pannable, zoomable chart in one component.

import { SLChart } from '@stacklatte/chart-core';
import type { Candle } from '@stacklatte/chart-core';
import { useWindowDimensions } from 'react-native';

const candles: Candle[] = [
  { timestamp: 1700000000000, open: 42000, high: 43500, low: 41800, close: 43100 },
  { timestamp: 1700003600000, open: 43100, high: 44200, low: 42900, close: 44000 },
  // ...
];

export default function TradingScreen() {
  const { width } = useWindowDimensions();
  return (
    <SLChart
      data={candles}
      width={width}
      height={400}
      theme="dark"
    />
  );
}

Line chart

<SLChart data={candles} width={width} height={400} chartType="line" theme="dark" />

Moving average overlays

Calculate SMA and EMA outside the component and pass them in as IndicatorLine objects.

import { SLChart, calcSMA, calcEMA } from '@stacklatte/chart-core';

const sma20 = calcSMA(candles, 20, 'sma-20', '#F5A623');
const ema9  = calcEMA(candles,  9, 'ema-9',  '#4a90e2');
const ema21 = calcEMA(candles, 21, 'ema-21', '#50E3C2');

<SLChart
  data={candles}
  indicators={[sma20, ema9, ema21].filter(Boolean)}
  width={width}
  height={400}
/>

Bollinger Bands

Bollinger Bands return three indicator lines (upper, basis, lower). Pass a ShadedArea to fill the region between the bands.

import { SLChart, calcBollingerBands } from '@stacklatte/chart-core';

const bb = calcBollingerBands(candles, 20, 2, {
  upper: '#9B59B6',
  basis: '#7F8C8D',
  lower: '#9B59B6',
});

<SLChart
  data={candles}
  indicators={bb ? [bb.upper, bb.basis, bb.lower] : []}
  shadedAreas={bb ? [{ fromId: 'bb-upper', toId: 'bb-lower', color: '#9B59B6', opacity: 0.1 }] : []}
  width={width}
  height={400}
/>

RSI sub-panel

Displays a Relative Strength Index panel below the main chart. RSI is computed internally using Wilder smoothing (period 14) on the visible candles.

<SLChart
  data={candles}
  showRsiPanel
  width={width}
  height={480}   // extra height for the sub-panel
/>

The panel includes 30/70 overbought/oversold threshold lines and a synchronized crosshair.


MACD sub-panel

MACD (12, 26, 9) — line, signal, and histogram bars — in a panel synchronized with the main chart.

<SLChart
  data={candles}
  showMacdPanel
  width={width}
  height={480}
/>

Run both panels at once:

<SLChart
  data={candles}
  showRsiPanel
  showMacdPanel
  width={width}
  height={560}
/>

OHLC HUD

An overlay that shows Open, High, Low, Close values for the currently inspected candle — and any indicator values at that timestamp.

<SLChart
  data={candles}
  showOhlcHud
  indicators={[ema9, ema21]}
  width={width}
  height={400}
/>

When no touch is active, the HUD shows the latest candle. On tap or drag it locks to the touched candle.


Crosshair callback

<SLChart
  data={candles}
  showOhlcHud
  onCrosshairChange={(candle) => {
    if (candle) {
      console.log(`Price: ${candle.close} at ${new Date(candle.timestamp).toISOString()}`);
    }
  }}
  width={width}
  height={400}
/>

Zoom and pan

Pinch to zoom and pan are built-in — no setup required.

<SLChart
  data={candles}
  visibleDataPoints={60}   // candles visible at initial load
  maxZoom={400}            // maximum candles visible (widest zoom out)
  width={width}
  height={400}
/>

| Gesture | Action | |---|---| | 1-finger pan | Scroll the time axis | | 2-finger pinch | Zoom in/out around the focal point | | Tap or pan (with showOhlcHud) | Lock the crosshair to a candle | | Pan to the right edge | Re-engage live-follow mode |


Live / streaming data

The chart auto-follows new candles when the viewport is at the latest data. After the user scrolls away, increment scrollToLatestTrigger to snap back.

const [candles, setCandles] = useState<Candle[]>(initialCandles);
const [trigger, setTrigger] = useState(0);

function onNewCandle(updatedCandles: Candle[]) {
  setCandles(updatedCandles);
  setTrigger(t => t + 1);   // snaps back to latest
}

<SLChart
  data={candles}
  scrollToLatestTrigger={trigger}
  width={width}
  height={400}
/>

Themes

Built-in presets

<SLChart data={candles} theme="dark"  width={width} height={400} />
<SLChart data={candles} theme="light" width={width} height={400} />

Custom theme

Pass any ChartThemeColors object to override every color token.

import type { ChartThemeColors } from '@stacklatte/chart-core';

const myTheme: ChartThemeColors = {
  background:    '#0d0d0d',
  gridH:         '#1a1a1a',
  gridV:         '#141414',
  priceLabel:    '#999999',
  timeLabel:     '#555555',
  crosshair:     '#777777',
  candleUp:      '#26a69a',
  candleDown:    '#ef5350',
  lineChart:     '#00e5ff',
  rsiLine:       '#f1c40f',
  rsiThreshold:  '#2a2a2a',
  hudBackground: 'rgba(0,0,0,0.85)',
  hudText:       '#ffffff',
};

<SLChart data={candles} theme={myTheme} width={width} height={400} />

All together

import { SLChart, calcEMA, calcBollingerBands } from '@stacklatte/chart-core';
import type { Candle, IndicatorLine, ShadedArea } from '@stacklatte/chart-core';
import { useMemo, useState } from 'react';
import { useWindowDimensions } from 'react-native';

export default function TradingScreen({ candles }: { candles: Candle[] }) {
  const { width } = useWindowDimensions();
  const [trigger, setTrigger] = useState(0);

  const { indicators, shadedAreas } = useMemo(() => {
    const lines: IndicatorLine[]  = [];
    const areas: ShadedArea[]     = [];

    const ema9  = calcEMA(candles, 9,  'ema-9',  '#4a90e2');
    const ema21 = calcEMA(candles, 21, 'ema-21', '#50E3C2');
    if (ema9)  lines.push(ema9);
    if (ema21) lines.push(ema21);

    const bb = calcBollingerBands(candles, 20, 2, {
      upper: '#9B59B6', basis: '#7F8C8D', lower: '#9B59B6',
    });
    if (bb) {
      lines.push(bb.upper, bb.basis, bb.lower);
      areas.push({ fromId: 'bb-upper', toId: 'bb-lower', color: '#9B59B6', opacity: 0.1 });
    }

    return { indicators: lines, shadedAreas: areas };
  }, [candles]);

  return (
    <SLChart
      data={candles}
      indicators={indicators}
      shadedAreas={shadedAreas}
      width={width}
      height={560}
      theme="dark"
      chartType="candle"
      showOhlcHud
      showRsiPanel
      showMacdPanel
      visibleDataPoints={60}
      scrollToLatestTrigger={trigger}
      onCrosshairChange={(c) => c && console.log('candle', c.close)}
    />
  );
}

Standalone indicator calculators

All calculators are exported for use outside the chart component — useful for displaying values in a separate UI element.

import { calcSMA, calcEMA, calcBollingerBands, calcRSI, calcMACD } from '@stacklatte/chart-core';
import type { RsiPoint, MacdResult } from '@stacklatte/chart-core';

const sma   = calcSMA(candles, 20, 'sma-20', '#F5A623');   // IndicatorLine | null
const ema   = calcEMA(candles, 20, 'ema-20', '#4a90e2');   // IndicatorLine | null
const bb    = calcBollingerBands(candles, 20, 2, colors);  // { upper, basis, lower } | null
const rsi   = calcRSI(candles, 14);                        // RsiPoint[]
const macd  = calcMACD(candles, 12, 26, 9);                // MacdResult | null

Props

| Prop | Type | Default | Description | |---|---|---|---| | data | Candle[] | required | OHLC candles sorted ascending by timestamp (ms) | | width | number | required | Canvas width in logical pixels | | height | number | required | Canvas height in logical pixels | | theme | "dark" \| "light" \| ChartThemeColors | "dark" | Color preset or full custom theme | | chartType | "candle" \| "line" | "candle" | Main series rendering style | | indicators | IndicatorLine[] | [] | Overlay lines (SMA, EMA, Bollinger Bands) | | shadedAreas | ShadedArea[] | [] | Filled band between two indicator lines | | visibleDataPoints | number | 60 | Number of candles visible at initial load | | intervalMs | number | auto | Milliseconds per candle — inferred from data if omitted | | showGrid | boolean | true | Horizontal and vertical grid lines | | showOhlcHud | boolean | false | OHLC info overlay | | showRsiPanel | boolean | false | RSI sub-panel | | showMacdPanel | boolean | false | MACD sub-panel | | maxZoom | number | data length | Maximum candles visible at once | | hour12 | boolean | false | 12-hour (AM/PM) time axis | | scrollToLatestTrigger | number | — | Increment to snap to latest candle | | onCrosshairChange | (candle: Candle \| null) => void | — | Fires on crosshair lock/release |


Data format

interface Candle {
  timestamp: number; // Unix milliseconds, sorted ascending
  open:      number;
  high:      number;
  low:       number;
  close:     number;
}

Architecture

Each drawing concern is a separate Skia component. Swap or extend individual layers without touching the rest.

| Layer | Renders | |---|---| | AxisLayer | Time/price grid lines and labels | | CandlestickLayer | OHLC wicks and bodies | | LineLayer | Close-price line | | IndicatorLayer | Single indicator polyline | | ShadedAreaLayer | Filled region between two series | | RsiLayer | RSI sub-panel with 30/70 threshold lines | | MacdLayer | MACD sub-panel (line, signal, histogram) | | CrosshairLayer | Dashed vertical + horizontal crosshair | | OhlcHud | OHLC info overlay |

All layers are exported for custom compositions:

import { SLChart, AxisLayer, CandlestickLayer, IndicatorLayer } from '@stacklatte/chart-core';

Contributing

Issues and PRs welcome. See the monorepo README for setup instructions.


License

MIT — see LICENSE.md.