# Openalgo Chart Plugin

> 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.

- Skill: `marketcalls/openalgo-chart-plugin` (Agent Skill)
- Install (CLI): `npx skillmds@latest add marketcalls/openalgo-chart-plugin`
- Raw SKILL.md: https://api.skillmd.com/api/skills/marketcalls/openalgo-chart-plugin/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: marketcalls (https://skillmd.com/u/marketcalls)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/marketcalls/openalgo-chart-plugin

---


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

- Primitive and custom chart type: [primitives-and-plugins](../openalgo-charts/references/primitives-and-plugins.md)
- Drawing tool: [drawing-tools](../openalgo-charts/references/drawing-tools.md)
- Indicator descriptor: [indicators](../openalgo-charts/references/indicators.md)

Confirm the interface shape against local typings before writing an implementation:

```sh
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

1. **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.
2. **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.
3. **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.
4. **`autoscaleInfo` runs every layout pass.** Return a cheap precomputed extent; do not scan your data there.
5. **Give hit-testable primitives a stable `externalId`.** Clicks and drags route back to you through it.
6. **Clean up in `detached`.** Timers, subscriptions, cached bitmaps.
7. **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](../openalgo-charts/references/bundling-and-tiers.md).
8. **Register before use.** For a chart type, before the `addSeries` call that names it.
9. No emojis or icons in drawn text or logs.

## Step 4 - minimum viable primitive

```ts
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, max: this.hi }; }
}

chart.addPrimitive(new Band(99, 101), 0);
```

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.

```sh
npx vitest run
npx tsc --noEmit
```

Report the extension point you chose and why, the file you created, and the test that covers it.

