The plugin family # 16 packages
Everything here is an opt-in package, not core bytes: each plugin is a separate npm package, zero dependencies, built only on wickchart's public layer API and kept small by its own CI gzip budget. The core chart stays lean — you pay bytes only for what you attach, and everything detaches cleanly.
Every plugin follows the same shape: attach → configure → detach. Install any of them next to the chart:
npm install wickchart wickchart-sessions // …or wickchart-draw, wickchart-tape, … import 'wickchart'; // the chart itself import { attachSessions } from 'wickchart-sessions'; const sessions = attachSessions(chart, { preset: 'crypto' });
…or straight from the CDN, no install: import { attachTape } from 'https://unpkg.com/wickchart-tape'.
| Package | One-liner | Size | Peer |
|---|---|---|---|
| wickchart-draw | drawing tools — trendlines, levels, rects, fibs, text; JSON in/out | ~8 KB | ≥ 1.4 |
| wickchart-sessions | market-session shading, DST-correct, presets or custom windows | 6.3 KB | ≥ 1.4 |
| wickchart-replay | bar replay — step/seek/play with the future hidden | 3.1 KB | ≥ 1.4 |
| wickchart-animate | live-price easing — ticks glide to their new close | 2.3 KB | ≥ 2.3 |
| wickchart-compare | normalized multi-symbol overlays with live legend chips | 4.4 KB | ≥ 1.4 |
| wickchart-navigator | range navigator docked below the chart | 3.2 KB | ≥ 1.6 |
| wickchart-alerts-plus | alert persistence, sound, desktop notifications, webhooks | 2.7 KB | ≥ 1.4 |
| wickchart-layouts | named workspace snapshots — save/load/export full chart state | 2.3 KB | ≥ 1.4 |
| wickchart-signals | engulfing / pin bar / inside-bar badges with hover explanations | 3.6 KB | ≥ 1.4 |
| wickchart-tape | time & sales — live prints docked below the chart, trades→bars | 4.6 KB | ≥ 1.6 |
| wickchart-narrator | guided playback — narrated timeline, bar-walk, sonification, story tours | 8.6 KB | ≥ 1.4 |
| wickchart-coview | cross-tab co-viewing — presence bands, shared crosshair, open protocol | 5.0 KB | ≥ 1.4 |
| wickchart-scenario | planning — σ-cone projections, ghost paths, R-multiple risk plans | 2.1 KB | ≥ 1.4 |
| wickchart-ai | the chart as an LLM tool surface — manifest, prompt, validated ops | 2.2 KB | ≥ 1.4 |
insetBottom hook and share the single strip at the bottom of the canvas — attach one or the other. Every playground below is live: click around.wickchart-draw # ~8 KB gz
Drawing tools as a layer: trendline / ray / infinite line, horizontal level, rectangle, fibonacci retracement and text notes with an inline editor. Drawings are plain { time, price } data — they ride zoom & pan, survive data reloads and serialize to JSON. Anchors magnet-snap to bar times and OHLC prices.
import { attachDrawings } from 'wickchart-draw'; const draw = attachDrawings(chart, { magnet: true, color: '#4c8dff', width: 1.5 }); draw.setTool('trendline'); // arm a tool — dragging draws instead of panning draw.setTool(null); // select mode: click to select, drag to move/re-anchor draw.getDrawings(); // → JSON array (save it!); draw.setDrawings(saved) draw.undo(); draw.clear(); draw.deleteSelected(); draw.setShare('team-room'); // sync drawings across tabs (BroadcastChannel) chart.addEventListener('wick:drawings', (e) => save(e.detail.drawings)); // { drawings, action } chart.addEventListener('wick:drawselect', (e) => …); // { id }
wickchart-sessions # 6.3 KB gz
Translucent session bands under the price action: crypto (Asia / London / New York, UTC), forex (Tokyo / London / New York, UTC), NYSE and CME (real America/New_York / America/Chicago walls, DST-correct) — or fully custom windows. Optional closed-weekend tint and labels on each band. A passive crosshair bridge reports which session you're in; the layer never claims a pointer gesture.
import { attachSessions } from 'wickchart-sessions'; const s = attachSessions(chart, { preset: 'nyse' }); s.setPreset('crypto'); // 'crypto' | 'forex' | 'nyse' | 'cme' s.setSessions([{ name: 'Lunch', days: [1,2,3,4,5], start: '12:00', end: '13:00', tz: 'UTC' }]); s.setLabels(false); s.setWeekends(false); s.setOpacity(0.08); s.detach(); chart.addEventListener('wick:sessions', (e) => …); // { hover: name | null }
| Option / method | What it does |
|---|---|
preset | crypto · forex · nyse · cme — nyse/cme use IANA timezones with exact DST math |
setSessions(list) | custom bands { name, days, start, end, tz } — '24:00' ends at midnight; times 'HH:MM' |
setLabels / setWeekends / setOpacity | band labels on/off, closed-weekend tint on/off, band alpha (default 0.06) |
wickchart-replay # 3.1 KB gz
Bar replay on the public data API (setData + update) — the core stays replay-free. Start from any anchor (bar index, timestamp or date string — default ~70% of the dataset), then step or play while the future stays hidden. A badge shows the mode and position; loop mode wraps back to the anchor. If the dataset moves underneath (a live feed appends), replay aborts cleanly instead of corrupting state.
import { attachReplay } from 'wickchart-replay'; const r = attachReplay(chart, { speed: 4 }); // bars per second, 0.5..60 r.start('2026-03-04'); // index | ms | seconds | date string; omit → ~70% r.step(); r.play(); r.pause(); r.stop(); r.setSpeed(15); r.setLoop(true); r.seek(250); r.active; r.playing; r.index; r.total; r.remaining; r.detach(); chart.addEventListener('wick:replay', (e) => …); // { index, total, … }
wickchart-animate # 2.3 KB gz
Live-price easing: ticks on the forming bar glide to their new close over a short ease instead of teleporting. The engine wraps update() on the instance, so every tick source — app code, <wick-feed>, paper — is eased without wiring, and it is display-path only (no drawing, no new core surface). Eased frames rewrite the forming bar's close, which the body, last-price line, axis label, legend and stats all derive from; the final frame always writes the true bar, a new bar flushes the previous bar's true value, backfills pass through un-eased, retargets mid-ease continue from the display value without jumping, and prefers-reduced-motion is a hard passthrough.
import { attachAnimate } from 'wickchart-animate'; const anim = attachAnimate(chart, { duration: 180 }); // ms; 0 disables, max 1500 // easing: 'ease-out' (default) | 'linear' | fn(t) — volume: true eases the histogram too anim.detach();
duration of firing-time skew. Escape hatches: duration: 0 or detach(). Peer dependency: wickchart ≥ 2.3.wickchart-compare # 4.4 KB gz
Normalized multi-symbol overlays: up to six series, each drawn against its own invisible scale so the price axis is never distorted. Rebase to the first visible close ('first') or re-rebase live while panning ('visible'); derived ratio and diff series compute A/B on the fly. Legend chips show each symbol's live percent change.
import { attachCompare } from 'wickchart-compare'; const cmp = attachCompare(chart); cmp.setSeries([ { label: 'ETH', data: ethBars }, // explicit series { label: 'ETH/SOL', op: 'ratio', a: 'ETH', b: 'SOL' }, // derived from labeled ones ]); cmp.setRebase('visible'); // 'first' (default) | 'visible' — re-rebase while panning cmp.clear(); cmp.detach();
wickchart-alerts-plus # 2.7 KB gz
The pro tier for alerts. Core alerts are runtime-only by design; this plugin mirrors them into any storage (localStorage by default) at save points — adds, removes, fires, sync(), detach() — and re-arms them on reload. When a tab is hidden the browser notification covers it; a two-tone WebAudio beep and a webhook POST ride along.
import { attachAlertsPlus } from 'wickchart-alerts-plus'; const ap = attachAlertsPlus(chart, { key: 'my-alerts', // storage key (localStorage by default) notify: true, sound: true, // hidden-tab Notification + WebAudio beep webhook: 'https://…', // POST { id, price, when, time, bar, key } }); ap.add({ price: 100, direction: 'above' }); // → chart.addAlert + persisted ap.add({ when: 'rsi(close,14) < 30' }); // scripted alerts too ap.remove(id); ap.clear(); ap.list(); ap.sync(); ap.requestNotify(); // ask once for the notification permission ap.detach();
wick-docs-alerts — reload and they come back. Notifications/sound are off here to keep the docs quiet; the demo page shows them on.wickchart-layouts # 2.3 KB gz
Named workspace snapshots: save the whole setup — type, indicators, theme, view, positions, alerts (plus the drawing list when wickchart-draw is attached) — under a name, and restore it later. Deterministic newest-first ordering via a persisted monotonic sequence; cap of 20 layouts with oldest-eviction; export/import as JSON for sharing. In-memory by default, any storage via key.
import { attachLayouts } from 'wickchart-layouts'; const layouts = attachLayouts(chart, { drawings: draw, key: 'my-layouts' }); layouts.save('swing setup'); layouts.load('swing setup'); layouts.rename('swing setup', 'main'); layouts.delete('main'); layouts.list(); // → [{ name, at, drawingCount }] newest first layouts.export(); layouts.import(json); layouts.detach(); chart.addEventListener('wick:layouts', (e) => …); // { action, name }
wickchart-signals # 3.6 KB gz
Candlestick pattern badges: engulfing (E), pin bars (P — hammer / shooting star) and inside bars (IB) drawn as direction-colored chips above/below the bar. Hover a badged bar and the plugin draws the explanation and fires wick:signals — the same passive crosshair bridge as sessions, so gestures stay native. Detection is O(n) and cached per dataset + kind subset.
import { attachSignals } from 'wickchart-signals'; const sig = attachSignals(chart, { kinds: [] }); // start off — arm from your UI sig.setKinds(['engulfing', 'pinbar']); // subset; [] = off; omit = all three sig.setLabels(false); // hover explanations off sig.count; sig.kinds; sig.detach(); chart.addEventListener('wick:signals', (e) => …); // { index, time, signals, label }
wickchart-tape # 4.6 KB gz
Time & sales: a live trade-print strip docked at the bottom of the canvas — time · price · size rows colored by side, proportional size bars, oversized prints (bigSize) highlighting their row. Prints carry an optional side; without one the classic tick rule fills it in (uptick → buy, downtick → sell), carried across pushes. The same stream drives the chart: chart.setData(tape.toBars(60000)).
import { attachTape } from 'wickchart-tape'; const tape = attachTape(chart, { rows: 7, bigSize: 50 }); socket.onmessage = (m) => tape.push(m.trades); // single print or batch tape.set(backfill); // history backfill (resets the tick rule) tape.setRows(4); // 3..8 — resizes the dock live tape.hide(); tape.show(); // hiding frees the docked space entirely tape.trades; // normalized prints, newest last (cap 500) tape.toBars(60000); // prints → OHLCV bars, chart-ready tape.clear(); tape.detach(); chart.addEventListener('wick:tape', (e) => …); // { action, added?, total }
tradesToBars, formatting) is a separate import: wickchart-tape/core.wickchart-grid # 4.4 KB gz
Multi-chart layout + sync: one element lays N charts out in a CSS grid and keeps their visible ranges and crosshairs in step — pan or zoom any chart and the others follow; hover one and a dashed ghost crosshair mirrors onto the rest. Everything rides the core's public wick:range / wick:crosshair events, setVisibleRange() and the layer API; the fan-out swallows its own echoes and breaks clamp feedback loops, so sparse charts converge instead of ping-ponging.
<wick-grid cols="2" sync="range crosshair" style="height: 640px"> <wick-chart label="BTC · 1h" indicators="sma:20"></wick-chart> <wick-chart label="BTC · 15m" indicators="rsi:14"></wick-chart> </wick-grid> // or programmatically, on charts you already lay out: import { attachGrid } from 'wickchart-grid'; const grid = attachGrid([a, b, c], { sync: 'range time' }); grid.detach(); // unsync, ghost layers removed
Attributes: cols (default 2, clamped 1..8), gap (px, default 10), sync — a list of range (shared visible window), crosshair (time + price ghost lines), time (vertical line only — for grids whose price scales are not comparable), both; default range crosshair. The host needs a height: grid rows size from it.
wickchart-grid/core.wickchart-paper # 6.2 KB gz
Paper trading on top of wickchart-replay: place orders while the tape plays forward and watch them fill at honest prices — a market order placed while paused fills at the next bar's open (you can never trade a close you already saw), limits fill at the limit unless the open gaps through them. The session's equity curve docks under the chart (fills as dots, starting-cash baseline, max drawdown), and the open position mirrors onto the core positions API as an entry line with live P&L. Seeking back resets the session.
import { attachReplay } from 'wickchart-replay'; import { attachPaper } from 'wickchart-paper'; const replay = attachReplay(chart); const paper = attachPaper(chart, { cash: 10000, fee: 0.0004 }); replay.start(); // ~70% into the data, future hidden paper.buy(1); // queued — fills at the NEXT bar's open paper.flatten(); // market-close the whole position paper.stats; // equity, realized, trades, winRate, maxDD
Signed quantities net and flip (sell 5 against a long of 2 closes it and opens a short of 3); adds average the entry; fees come off cash per fill. Events on the chart: wick:paper (order / fill / close / cancel / reset). The engine is pure data in / data out — wickchart-paper/core — usable for backtests with no DOM at all.
wickchart-narrator # 8.6 KB gz
Guided playback: the chart as a story. narrate() turns a window into an ordered timeline — pivot highs/lows, volume spikes, gaps, RSI divergences and derived legs ("+12.4% over 38 bars"). walk() slides the viewport through history while wick:walk events announce each step's events; playRange() and the sonify attribute make the same data audible (a pitch sweep of the visible bars, one blip per crosshair bar); captureScene() / playStory() record chart state as scenes and replay them as a narrated tour — camera eases, indicators flip, scenarios set and clear. Any user input interrupts playback.
import { attachNarrator } from 'wickchart-narrator'; const narrator = attachNarrator(chart); // methods land on the instance chart.narrate({ from, to }); // the event timeline chart.walk({ from: 0, speed: 120 }); // narrated viewport replay chart.stopWalk(); // any pointer/wheel/key input stops it too chart.playRange(); // ~4s pitch sweep (needs a click first) const tour = [ chart.captureScene('Overview', 'The full picture'), { title: 'The breakout', range: { from, to }, indicators: 'sma:20' }, ]; chart.playStory(tour, { loop: true }); // wick:story narrates each scene narrator.detach(); chart.addEventListener('wick:walk', (e) => …); // { phase: step|end|stop, index, events }
Scenes are plain data — title, note, a time range, indicators, series type, overlays, scenario/riskPlan (or null to clear) and a dwell — capped at 20, junk dropped, dwell clamped to 500–30000 ms; playStory(story, { dwell, panMs, loop }) eases the camera between them and stopStory() / getStory() control the tour. Events: wick:walk { phase: 'step'|'end'|'stop', index, events, from, to } and wick:story { phase: 'scene'|'end'|'stop', index, total, scene, title, note }. Timeline entries carry { i, time, type, side, note, legPct?, legBars? }, sorted and capped at 60; walk({ from, to, speed, step }) any user input interrupts. The walk is a local replay of real history, not a projection — see scenario for what-ifs; during a walk the chart broadcasts its moving viewport over co-view, so peers watch the tour with you. First package of the 2.0 split: attaching shadows the core's own identical methods until 2.0 removes them.
wickchart/core — from a CDN, map that specifier with an import map). Part of the 2.0 plan.wickchart-coview # 5.0 KB gz
Cross-tab co-viewing: charts that share a room name (co-view="btc-warroom") keep each other briefed. Every pan/zoom broadcasts the visible window (throttled to ~8/s, heartbeat every 4 s), each chart renders the others' viewports as labeled bands along the top of the plot, and the crosshair is shared live — a dashed ghost mirrors the pointer of the tab you are pairing with. Closed tabs say goodbye; crashed tabs fade via the 12 s presence TTL.
<wick-chart co-view="btc-warroom" co-view-name="Maya"></wick-chart> import { attachCoview } from 'wickchart-coview'; const coview = attachCoview(chart); chart.getPeers(); // [{ id, name: 'Maya', range: {from,to}, at }] chart.addEventListener('wick:peers', (e) => …); // { peers, joined, left } coview.detach(); // the protocol is public — anything that can postMessage can join: // channel "wick-co-view:<room>", messages { v: 1, peer, type: view|cross|bye }
Membership events fire on join and leave only — not on every move. Wrong-version, self-echoed and unknown messages are ignored by construction. Transport is same-origin BroadcastChannel — tabs, windows, and multiple charts on one page; presence never leaves the browser. To sync across the network, forward the core's public wick:range events through your own WebSocket and replay them — the plugin ships no network code. The presence tracker and the protocol envelope are pure and DOM-free: wickchart-coview/core (PresenceTracker, coWrap/coUnwrap) — reusable over WebSockets. Second package of the 2.0 split; attaching shadows the core's identical machinery until 2.0 removes it.
wickchart-scenario # 2.1 KB gz
Planning surfaces drawn straight onto the chart. Scenario projections: a ghost path of future prices plus σ-bands — a volatility cone widening with √h from realized volatility — with future space reserved on the right so the projection stays visible. Risk plans: an R-multiple grid anchored at entry/stop (1R = |entry − stop|, direction derived), reward lines at kR with risk/reward zones shaded, so sizing and take-profit choices read directly off the chart.
import { attachScenario } from 'wickchart-scenario'; const scenario = attachScenario(chart); // methods land on the instance chart.setScenario({ path: [64000, 65500, 68000], label: 'bull case' }); chart.setScenario({ horizon: 48 }); // cone-only projection chart.scenario; // a copy of the active one chart.setRiskPlan({ entry: 64500, stop: 63800, multiples: [1, 2, 3] }); chart.setRiskPlan({ entry: 64500, stop: 63800, targets: [65900, 67300] }); // prices → kR chart.riskPlan; // { entry, stop, risk, direction, levels } chart.clearScenario(); chart.clearRiskPlan(); scenario.detach();
Invalid specs are dropped, never thrown — and clear what was there before (replace semantics). Both are analysis data, excluded from getState(). The pure surface (validators, the √h cone math, realized volatility) is a separate import: wickchart-scenario/core. Third package of the 2.0 split — the smallest attach layer of the family: four setters writing the state seams the element's renderer, cone cache and future-space reservation already read.
wickchart/core (CDN users: import map). Part of the 2.0 plan.wickchart-ai # 2.2 KB gz
The chart as an LLM tool surface — and nothing that touches the network. aiTools() is a self-describing tool manifest (get_data_window, set_indicators, set_overlays, add_alert, set_view, set_type, …); aiPrompt() is the matching system prompt; aiContext() grounds the model in the live state plus the visible-window summary; applyAI(ops) runs model answers through a validated dispatcher — ops are whitelisted, args checked, and a bad op resolves {ok:false, error} instead of throwing so the agent can self-correct. ask(instruction, {run}) wires it end to end with your model call.
import { attachAI } from 'wickchart-ai'; const ai = attachAI(chart); // methods land on the instance const { results } = await chart.ask('add RSI and mark the demand zone', { run: async (payload) => (await callMyLLM(payload)).ops, // your model, your keys }); // or two-step: build the payload, ship it anywhere, apply the answer const { payload } = await chart.ask('switch to a line chart'); chart.applyAI([{ tool: 'set_type', args: { type: 'line' } }]); ai.detach();
The chart itself never touches the network — run is yours, so keys and endpoints stay in your code. getDataWindow() stays in core by design (a data API, not an LLM API); ask() composes it. The manifest + prompt + dispatcher are pure: wickchart-ai/core. Fourth and final package of the 2.0 split — attaching shadows the core's identical methods until 2.0 removes them.
wickchart/core (CDN users: import map). Part of the 2.0 plan.