# Drawing tools

> Trend lines, Fibonacci levels, shapes and notes, with magnet, undo and JSON.

Section: Charts. Source: https://ortexcharts.com/docs/drawings

## Attaching the Tools

Drawing tools are a primitive on a pane. Create them once and the returned object is the
whole API: starting a tool, adding drawings from code, selecting, deleting, undo, redo and
serialization.

```ts
import { createDrawingTools, createFinancialChart } from "@ortex-charts/financial";

const chart = createFinancialChart(el, { data: bars, resolution: "1D" });

const tools = createDrawingTools(chart.chart, {
  magnetPx: 8,
  onChange: (change) => {
    if (change.type !== "selected" && change.type !== "toolChanged") {
      saveDrawings(tools.getDrawings());
    }
  },
});
```

Using [the shell](https://ortexcharts.com/docs/shell) instead gives you the same primitive with a tool rail already
wired to it, reachable as `shell.drawings`.

## The 32 Tools

Every kind below is placed by clicking, moved by dragging, edited by its handles, hit-tested
for tooltips and serialized to the same JSON shape. `POINTS_REQUIRED` is exported, so a
custom tool rail can show progress while a drawing is being placed.

### Lines and Freehand

| Tool | `kind` | Points | Notes |
|---|---|---|---|
| Trend line | `trendline` | 2 | Extends left, right, both or neither through `style.extend`. |
| Ray | `ray` | 2 | A trend line that always extends right. |
| Horizontal line | `horizontalLine` | 1 | Price line across the pane with a value label. |
| Horizontal ray | `horizontalRay` | 1 | A price line from its anchor rightward. |
| Vertical line | `verticalLine` | 1 | Time line across the pane. |
| Arrow | `arrow` | 2 | A line with a head at the second point, labeled from `style.text`. |
| Curved arrow | `curvedArrow` | 3 | The third point bends the curve. |
| Polyline | `polyline` | Open ended | A click per vertex, ended by a double-click or Enter. |
| Brush | `brush` | Open ended | Samples the pointer while the button is held, smoothed. |

### Shapes and Channels

| Tool | `kind` | Points | Notes |
|---|---|---|---|
| Rectangle | `rectangle` | 2 | Filled at `style.fillOpacity`, optional label from `style.text`. |
| Ellipse | `ellipse` | 2 | The two points are opposite corners of its bounding box. |
| Triangle | `triangle` | 3 | Filled at `style.fillOpacity`. |
| Parallel channel | `parallelChannel` | 3 | Two points define the base line, the third the width. |
| Regression trend channel | `regressionChannel` | 2 | A least-squares fit between the two times, with a band at `style.deviation` standard deviations either side. |

### Fibonacci, Gann and Pitchfork

| Tool | `kind` | Points | Default levels |
|---|---|---|---|
| Fibonacci retracement | `fibRetracement` | 2 | 0, 0.236, 0.382, 0.5, 0.618, 0.786, 1 |
| Fibonacci extension | `fibExtension` | 3 | 0, 0.618, 1, 1.618, 2.618, 3.618, 4.236 |
| Fibonacci fan | `fibFan` | 2 | 0.236, 0.382, 0.5, 0.618, 0.786 |
| Fibonacci arcs | `fibArcs` | 2 | 0.382, 0.5, 0.618, 1 |
| Fibonacci time zones | `fibTimeZone` | 2 | 0, 1, 2, 3, 5, 8, 13, 21, 34, 55, counted in bars |
| Fibonacci channel | `fibChannel` | 3 | 0, 0.236, 0.382, 0.5, 0.618, 1, 1.618 |
| Gann fan | `gannFan` | 2 | 1/8, 1/4, 1/3, 1/2, 1, 2, 3, 4, 8, as price per bar |
| Pitchfork | `pitchfork` | 3 | 1, the median line and its two tines; add 0.5 for the half lines |

All eight read `style.levels`, so replacing the array changes which lines are drawn.
`LEVELS_BY_KIND` holds the sets that differ from the retracement default, and `levelsFor`
returns the one a kind starts with.

### Measurement and Position

| Tool | `kind` | Points | Notes |
|---|---|---|---|
| Measure | `measure` | 2 | Price change, percent change and bar count between two points. |
| Price range | `priceRange` | 2 | The vertical move alone, colored up or down. |
| Date range | `dateRange` | 2 | The horizontal span alone, in bars and in elapsed time. |
| Date and price range | `datePriceRange` | 2 | Both readouts in one box. |
| Long position | `longPosition` | 3 | Entry, target and stop, shaded profit and loss, with the reward-to-risk ratio. |
| Short position | `shortPosition` | 3 | The same, inverted. |

### Annotation

| Tool | `kind` | Points | Notes |
|---|---|---|---|
| Text | `text` | 1 | A text note anchored to a bar and a price. |
| Callout | `callout` | 2 | Text in a rounded box with a tail to the anchor. |
| Note with a leader line | `note` | 2 | A dot at the anchor, an underlined note at the second point. |
| Price label | `priceLabel` | 1 | The price at a bar as a filled tag, prefixed by `style.text`. |

`TEXT_KINDS` names the three that ask the host for their text as they are placed, through
`requestText`.

### What the Rail Shows

The shell's tool rail shows nine of these by default, because 32 icons crowd a rail: trend
line, ray, horizontal line, vertical line, rectangle, parallel channel, Fibonacci
retracement, measure and text. Its `drawingTools` option names the set you want instead, in
the order you want it. See [The toolbar](https://ortexcharts.com/docs/toolbar).

TradingView ships roughly 90 drawing tools, counting the variations within each family. The
32 here are the ones a chart reaches for: the whole Fibonacci set, the channels, the ranges,
the position tools and the annotations. Pattern tools, Elliott wave and XABCD among them,
are the family that is not here.

## Drawing From the User Interface

```ts
tools.startTool("trendline");         // next clicks place the points
tools.startTool("fibRetracement", { color: "#F5A524", extend: "right" });
tools.cancelTool();
```

While a tool is active, each click places a point and the drawing completes when it has the
points it needs. Escape cancels. Once a drawing exists, clicking selects it, dragging moves
it, and dragging a handle moves one point.

Keyboard behavior on a focused chart:

| Key | Effect |
|---|---|
| Escape | Cancel the active tool, or clear the selection. |
| Delete or Backspace | Remove the selected drawing. |
| Ctrl or Cmd + Z | Undo. |
| Ctrl or Cmd + Shift + Z, Ctrl + Y | Redo. |

### Magnet

`magnetPx` snaps a placed or dragged price to the nearest open, high, low or close of the
bar under the cursor, when one is within that many pixels. It defaults to eight. Setting it
to zero disables snapping entirely, which is what a chart of a continuous value rather than
of bars wants.

## Drawing From Code

```ts
tools.add({
  kind: "trendline",
  points: [
    { time: bars[120].time, price: bars[120].low },
    { time: bars[300].time, price: bars[300].high },
  ],
  style: { color: "#22C08A", extend: "right", text: "Support" },
  meta: { source: "auto-detected", confidence: 0.8 },
});
```

Only `kind` and `points` are required; the id, style, `locked` and `visible` are filled in.
`meta` is carried through serialization untouched, which is where you keep the identifier
that links a drawing back to whatever produced it.

```ts
tools.updateDrawing(id, { style: { color: "#EF4E5A" }, locked: true });
tools.remove(id);
tools.clear();
tools.select(id);          // or null to clear the selection
tools.selected;            // a copy of the selected drawing, or null
tools.undo();
tools.redo();
```

A locked drawing is drawn and hit-tested but cannot be moved, which is the right state for
levels your application computed rather than the user drew.

## The Drawing Shape

Drawings are plain JSON. Points are anchored to data, not to pixels, so they survive zooming,
panning, a resolution change and a window resize.

```ts
interface Drawing {
  id: string;
  kind: DrawingKind;
  points: Array<{ time: number; price: number }>;
  style: DrawingStyle;
  locked: boolean;
  visible: boolean;
  meta?: unknown;
}

interface DrawingStyle {
  color: string;               // empty string means the theme line color
  lineWidth: number;           // 1.5
  lineStyle: "solid" | "dashed" | "dotted";
  fillOpacity: number;         // 0.12
  extend: "none" | "right" | "left" | "both";
  text?: string;
  fontSize: number;            // 12
  levels?: number[];           // Fibonacci ratios
  deviation: number;           // 2, the regression channel half-width in standard deviations
  showLabels: boolean;         // true
}
```

The Fibonacci defaults are `[0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]`. Replace the array to
change which levels are drawn, including adding extension levels above one:

```ts
tools.add({
  kind: "fibRetracement",
  points: [low, high],
  style: { levels: [0, 0.382, 0.5, 0.618, 1, 1.618, 2.618] },
});
```

`DRAWING_STYLE_DEFAULTS` is exported if you want to start from the defaults and change one
field.

## Saving and Restoring

```ts
const json = JSON.stringify(tools.getDrawings());
tools.setDrawings(JSON.parse(json));
```

That is the whole persistence story. The chart never writes to storage. Where drawings live
is your decision, and the two common answers are different:

- **Per symbol.** A trend line drawn on AAPL should be there on every chart of AAPL. This is
  the most-requested behavior in the category and it is a storage question, so
  [the shell](https://ortexcharts.com/docs/shell) takes a `drawingsStore` with `load(symbol)` and
  `save(symbol, drawings)` and does the rest.
- **Per layout.** A drawing that belongs to one saved view. Pass
  `layoutIncludesDrawings: true` to the shell and drawings ride along inside the
  [layout](https://ortexcharts.com/docs/shell) JSON.

Use one or the other, not both.

## Change Events

```ts
createDrawingTools(chart.chart, {
  onChange: (c) => {
    switch (c.type) {
      case "created": console.log("added", c.drawing.kind); break;
      case "updated": console.log("moved", c.drawing.id); break;
      case "deleted": console.log("removed", c.id); break;
      case "selected": setSelected(c.id); break;
      case "toolChanged": setActiveTool(c.kind); break;
    }
  },
});
```

`selected` and `toolChanged` fire for interface state and do not change the data, so
persistence should ignore them, which is why the example at the top of this page filters
them out.

## Other Hooks

```ts
createDrawingTools(chart.chart, {
  paneId: rsiPane.id,                            // tools on a pane other than the first
  priceScaleId: "left",                          // anchor prices to another scale
  defaultStyle: { color: "#4C8DFF", lineWidth: 2 },
  requestText: async () => window.prompt("Note") ?? null,
  onEdit: (drawing) => openMyStyleDialog(drawing),
  priceFormatter: (price) => `$${price.toFixed(2)}`,
});
```

- `requestText` is called when a text drawing is placed. It defaults to `window.prompt`;
  replace it with your own modal to keep the interface consistent.
- `onEdit` fires on a double-click, which is where a style dialog belongs.
- `priceFormatter` controls the labels on measures, Fibonacci levels and horizontal lines;
  it defaults to the main series price format.
- One instance covers one pane. Drawing on two panes means two instances.

## Hit Testing and Tooltips

Drawings take part in the chart hit test, so a hovered drawing reports through
`chart.subscribeCrosshairMove` in the `hit` field of the event, and a click reports through
`chart.subscribeClick`.

```ts
chart.chart.subscribeClick((e) => {
  if (!e.hit) return;
  const payload = e.hit.data as { drawing: Drawing; handle: number } | undefined;
  if (payload) openDetailPanel(payload.drawing.id, payload.drawing.meta);
});
```

`hit.id` is the drawing id, `hit.data.drawing` is a copy of the drawing including your
`meta`, and `hit.data.handle` is the index of the handle under the pointer or −1 for the
body. That is enough to open your own context menu or detail panel.
