| name | openalgo-chart-plugin |
| description | Extend openalgo-charts with a custom primitive, drawing tool, chart type, or indicator descriptor. Use when the user wants a custom overlay, annotation, band, zone, marker layer, hand-drawn tool, or a series style the library does not ship. |
| argument-hint | [what-to-build] |
| allowed-tools | Read, Write, Edit, Bash, Glob, Grep |
Extend the engine. Everything extensible in this library is a registry entry or a primitive - there is no plugin loader and no core file to edit.
Step 1 - pick the extension point
Answer these in order and stop at the first yes.
| Question | Build |
|---|
| Does it need its own data, autoscale and price-scale identity? | a chart type via registerChartType |
| Is it a shape the user places with clicks and drags later? | a drawing tool via registerDrawingTool |
| Is it a number computed per bar that should get a pane, legend row and settings? | an indicator via registerIndicator |
| Anything else that draws - band, zone, label, watermark, custom axis marker | a primitive via IPrimitive |
Default to a primitive. It is the cheapest and most flexible. Reach past it only when the table above says to.
Step 2 - read the contract before writing
Confirm the interface shape against local typings before writing an implementation:
rg -n "interface IPrimitive|interface RendererEntry|interface DrawingTool|interface IndicatorDescriptor" \
node_modules/openalgo-charts/dist/index.d.ts node_modules/openalgo-charts/dist/draw/index.d.ts
Step 3 - the rules that make it correct
- Draw in bitmap pixels. The canvas context is scaled to the device. Multiply every media-px value by
ctx.dpr before you draw, or the result blurs and drifts on HiDPI displays. This is the single most common mistake.
- Anchor in data space, not pixels. Store
{ time, price } and convert at draw time. The time axis is gapless, so a pixel anchor slides the moment a session gap collapses or the user zooms.
- Pick the right z-order.
'bottom' draws behind the series, 'normal' over it on the base canvas, 'top' on the overlay canvas which repaints on cursor moves. Anything that follows the pointer belongs on 'top'; anything static does not, or you will repaint it on every mouse move.
autoscaleInfo runs every layout pass. Return a cheap precomputed extent; do not scan your data there.
- Give hit-testable primitives a stable
externalId. Clicks and drags route back to you through it.
- Clean up in
detached. Timers, subscriptions, cached bitmaps.
- Register into the package entry, never a deep path. A deep import creates a second registry Map and your registration becomes invisible to
createChart. See bundling-and-tiers.
- Register before use. For a chart type, before the
addSeries call that names it.
- No emojis or icons in drawn text or logs.
Step 4 - minimum viable primitive
import type { IPrimitive, PrimitiveRenderContext } from 'openalgo-charts';
class Band implements IPrimitive {
constructor(private lo: number, private hi: number) {}
zOrder() { return 'bottom' as const; }
draw(g: CanvasRenderingContext2D, ctx: PrimitiveRenderContext) {
const dpr = ctx.dpr;
const y1 = ctx.priceScale.priceToY(this.hi) * dpr;
const y2 = ctx.priceScale.priceToY(this.lo) * dpr;
g.fillStyle = 'rgba(80,140,255,0.12)';
g.fillRect(0, y1, ctx.plotWidth * dpr, y2 - y1);
}
autoscaleInfo() { return { min: this.lo, : . }; }
}
chart.( (, ), );
Confirm every member name against the typings before shipping this shape - the reference file documents the interface, but dist/index.d.ts is authoritative.
Step 5 - test it
The repo's own tests are the pattern to copy. Look at tests/primitives.test.ts, tests/draw-tier.test.ts and tests/chart-types.test.ts for how a canvas-free unit test asserts draw calls and hit-testing. A plugin with no test is not done.
npx vitest run
npx tsc --noEmit
Report the extension point you chose and why, the file you created, and the test that covers it.