@krag-labs/krag-chart
v1.0.4
Published
High-performance WebAssembly candlestick chart with volume footprint, powered by ImPlot
Downloads
275
Maintainers
Readme
krag-chart
High-performance WebAssembly candlestick chart with volume footprint, powered by ImPlot and ImGui. Part of the KRAG Labs trading stack.
System Architecture
@startuml
!theme plain
skinparam backgroundColor #0d1117
skinparam defaultFontColor #c9d1d9
skinparam ArrowColor #8b949e
skinparam componentBorderColor #30363d
skinparam componentBackgroundColor #161b22
skinparam packageBorderColor #30363d
skinparam packageBackgroundColor #0d1117
skinparam noteFontColor #8b949e
skinparam noteBorderColor #30363d
skinparam noteBackgroundColor #161b22
package "External" {
[Binance Exchange\n(WebSocket / REST)] as Binance #1a2332
}
package "KRAG Labs Backend" {
[binance-collector\n(Go gRPC server)] as Collector #1a2332
note right of Collector
port 50051 — gRPC (backend)
port 50052 — Connect HTTP (browser)
end note
}
package "krag-trading-ui (React + Vite)" {
component "TanStack Query" as Query {
[useCandles()] as UC
[useFootprint()] as UF
[useStreamTrades()] as UST
}
[KragChartWidget.tsx] as Widget
[stream-store\n(Zustand)] as Store
}
package "krag-chart (WebAssembly)" {
[ImGui / ImPlot\nC++ → WASM] as WASM #1a2332
note right of WASM
bridge.cpp
extern "C" push API
end note
}
Binance --> Collector : WebSocket / REST
Collector --> UC : GetCandles RPC\n(Connect JSON)
Collector --> UF : GetFootprint RPC\n(Connect JSON)
Collector --> UST : StreamTrades RPC\n(NDJSON stream)
UC --> Widget : pushCandles()
UF --> Widget : pushFootprint()
UST --> Store : trade events
Store --> Widget : live trades
Widget --> WASM : pushCandles()\npushFootprint()\npushTrade()\nextern "C" / ccall
@endumlData Flow
1. Historical data (on symbol/timeframe change)
@startuml
!theme plain
skinparam backgroundColor #0d1117
skinparam defaultFontColor #c9d1d9
skinparam ArrowColor #58a6ff
skinparam sequenceParticipantBorderColor #30363d
skinparam sequenceParticipantBackgroundColor #161b22
skinparam sequenceArrowColor #8b949e
skinparam sequenceLifeLineBorderColor #30363d
participant "React\nKragChartWidget" as React
participant "binance-collector\n:50052" as Collector
participant "krag-chart.wasm\n(bridge.cpp)" as WASM
participant "ImPlot\nrenderer" as ImPlot
React -> Collector : POST /GetCandles\n{symbol, intervalMs, limit}
Collector --> React : {candles: [...]}
React -> Collector : POST /GetFootprint\n{symbol, intervalMs, tickSize}
Collector --> React : {candles: [{levels:[...]}]}
React -> WASM : pushCandles(json)\nextern "C" ccall
note right : populates TickerData\nrecalculates Bollinger Bands
React -> WASM : pushFootprint(json)\nextern "C" ccall
note right : maps to FootprintCandle[]\ndetects imbalances
WASM -> ImPlot : renders frame
@enduml2. Live data (continuous streaming)
@startuml
!theme plain
skinparam backgroundColor #0d1117
skinparam defaultFontColor #c9d1d9
skinparam sequenceParticipantBorderColor #30363d
skinparam sequenceParticipantBackgroundColor #161b22
skinparam sequenceArrowColor #8b949e
skinparam sequenceLifeLineBorderColor #30363d
participant "binance-collector\n:50052" as Collector
participant "useStreamTrades\n(React hook)" as Hook
participant "stream-store\n(Zustand)" as Store
participant "KragChartWidget" as Widget
participant "krag-chart.wasm" as WASM
loop NDJSON server-stream
Collector -> Hook : {"result": {trade}}
Hook -> Store : pushTrade(trade)
Store -> Widget : trades[] updated
Widget -> WASM : pushTrade(json)\nextern "C" ccall
note right : accumulates into\nopen candle footprint
WASM -> WASM : re-renders next frame\n(~60 fps WebGL)
end
note over Collector, WASM
On candle close: Widget calls pushCandleUpdate()
to append the finalized candle to TickerData
end note
@enduml3. WASM bridge (the connector layer)
The bridge lives in src/wasm/bridge.cpp. It is compiled into the WASM binary and exposes extern "C" functions that JavaScript calls via Emscripten's ccall:
// React → WASM
void pushCandles(const char* json_array); // batch load
void pushCandleUpdate(const char* json_object); // single live candle
void pushFootprint(const char* json_array); // pre-computed footprint
void pushTrade(const char* json_object); // single live trade
// WASM → React (data export)
const char* getKlinesJSON();
const char* getFootprintJSON();
int getDataSize();
int getFootprintSize();Packages
| Package | Location | Purpose |
|---|---|---|
| binance-collector | KRAG-Labs/binance-collector | Go gRPC server, collects from Binance |
| @krag-labs/krag-chart | KRAG-Labs/krag-chart (this repo) | WASM chart, published to npmjs.com |
| krag-trading-ui | KRAG-Labs/krag-trading-ui | React app, connects the two above |
npm Package Usage
Install
pnpm add @krag-labs/krag-chartNo auth required — the package is published publicly on npmjs.com. The C++ source stays private in this repo.
Basic React integration
import { useEffect, useRef } from 'react';
import KragChart from '@krag-labs/krag-chart';
function ChartContainer({ candles, footprint, trades }) {
const containerRef = useRef(null);
const chartRef = useRef(null);
useEffect(() => {
const chart = new KragChart(containerRef.current);
chartRef.current = chart;
chart.ready().then(() => {
// Hide built-in controls — React manages these
chart.showSymbolSelector(false);
chart.showLoadButton(false);
});
return () => chart.destroy();
}, []);
// Push historical candles when TanStack Query resolves
useEffect(() => {
chartRef.current?.pushCandles(candles);
}, [candles]);
// Push footprint data
useEffect(() => {
chartRef.current?.pushFootprint(footprint);
}, [footprint]);
// Push live trades one by one
useEffect(() => {
const latest = trades[trades.length - 1];
if (latest) chartRef.current?.pushTrade(latest);
}, [trades]);
return (
<div ref={containerRef} style={{ width: '100%', height: '600px', position: 'relative' }} />
);
}Candle data format (matches GetCandles response)
interface CandleData {
time: number; // Unix seconds
open: number;
high: number;
low: number;
close: number;
volume: number;
isClosed?: boolean; // false = live/open candle
}Footprint data format (matches GetFootprint response)
interface FootprintCandleData {
time: number; // Unix seconds (matches candle openTime)
tickSize?: number; // Price bucket size (e.g. 10.0 for BTC)
levels: Array<{
price: number;
buyVol: number;
sellVol: number;
buyImbalance?: boolean;
sellImbalance?: boolean;
}>;
}Trade data format (matches StreamTrades response)
interface TradeData {
time: number; // Unix seconds
price: number;
qty: number;
isSell: boolean;
}Building
Prerequisites
| Tool | Purpose | Install |
|---|---|---|
| Emscripten (emsdk) | Compile C++ → WASM | See below |
| CMake ≥ 3.21 | Build system | sudo pacman -S cmake |
| Ninja | Fast build | sudo pacman -S ninja |
| vcpkg | Native C++ deps | git clone https://github.com/microsoft/vcpkg |
Install Emscripten (CachyOS / Arch)
git clone https://github.com/emscripten-core/emsdk.git ~/emsdk
cd ~/emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.shAdd source ~/emsdk/emsdk_env.sh to your ~/.bashrc or ~/.config/fish/config.fish.
Build WASM
# One-shot build + copy to package/dist/
./build-wasm.sh
# With clean
./build-wasm.sh --clean
# Manual (using cmake preset)
source ~/emsdk/emsdk_env.sh
cmake --preset wasm-release
cmake --build build/wasm --parallelBuild native (for testing the chart standalone)
# Requires vcpkg
export VCPKG_ROOT=~/vcpkg
cmake --preset linux-debug
cmake --build build/linux-debug
./build/linux-debug/krag-chartWindows
call C:\emsdk\emsdk_env.bat
build-wasm.batPublishing to npm
The WASM binary is committed to package/dist/ or built locally. Publishing the artifact is safe — the C++ source stays private.
# Build WASM first
./build-wasm.sh
# Bump version and publish
cd package
npm version patch # or minor / major
npm publish # publishes @krag-labs/krag-chart publiclyRunning Tests
C++ unit tests (native build)
cmake --preset linux-debug
cmake --build build/linux-debug --target test_footprint test_bridge
./build/linux-debug/test_footprint
./build/linux-debug/test_bridgeJS integration test (browser)
Open package/test/test.html in a browser after building WASM:
./build-wasm.sh
# then open package/test/test.html via a local server
npx serve package/
# → http://localhost:3000/test/test.htmlWhat's Missing / Roadmap
| Priority | Item | Status | |---|---|---| | High | KragChartWidget.tsx in krag-trading-ui | In progress | | High | CI/CD: GitHub Actions WASM build + npm publish | Done | | Medium | Mock/demo candles when no data is pushed | Planned | | Medium | WASM memory cap (currently unbounded growth) | Planned | | Low | Dark/light theme API | Planned | | Low | Export chart as PNG via canvas | Planned |
Repository Structure
krag-chart/
├── src/
│ ├── Chart.cpp ← Main ImGui/ImPlot chart app (from ImStocks)
│ ├── App.cpp/h ← Window + GL setup
│ ├── BinanceTypes.cpp/h ← TickerData, Kline structs
│ ├── FootprintData.cpp/h ← FootprintCandle, PriceLevel, aggregation
│ ├── helpers.cpp/h ← ImPlot formatters, rendering helpers
│ ├── wasm/
│ │ └── bridge.cpp ← extern "C" push API (React → WASM)
│ ├── platform/
│ │ ├── native/ ← CURL + ixwebsocket (native builds)
│ │ └── web/ ← Emscripten fetch/websocket stubs
│ └── tests/
│ ├── test_footprint.cpp ← Unit tests: FootprintData aggregation
│ └── test_bridge.cpp ← Unit tests: bridge JSON parsing
├── package/ ← npm package (@krag-labs/krag-chart)
│ └── src/
│ ├── index.js ← KragChart JS class
│ └── index.d.ts ← TypeScript types
├── web/
│ └── shell.html ← Emscripten HTML template
├── CMakeLists.txt
├── CMakePresets.json ← linux-debug, linux-release, wasm-release, x64-*
├── vcpkg.json
├── build-wasm.sh ← Linux/CachyOS build script
└── build-wasm.bat ← Windows build scriptCredits
Chart rendering engine adapted from ImStocks by Evan Pezent. ImGui by Omar Cornut. ImPlot by Evan Pezent.
License
Copyright (c) 2025 KRAG Labs. See LICENSE.
