Drawing Tools
ApexStock's drawing layer is anchored in data space: every shape is stored against price and time coordinates, so it reprojects through zoom, pan, resize, a theme change, a chart-type switch, and streaming. A drawing made with the mouse and one made in code are the same object, and both appear in getDrawings().
const id = chart.addDrawing({
type: 'trendline',
points: [
{ x: '2024-01-08', y: 182.4 },
{ x: '2024-02-20', y: 197.1 },
],
color: '#2563eb',
width: 2,
})
Anchored types
| Type | Aliases | Points | Draws |
|---|---|---|---|
trendline | line | 2 | A straight line between two anchors |
ray | 2 | A line from the first anchor through the second, extended | |
horizontalLine | hline | 1 | A price level, from the first point's y |
verticalLine | vline | 1 | A time marker, from the first point's x |
rectangle | zone | 2 | A price and time box |
fibRetracement | 2 | Fibonacci levels between two anchor prices | |
fibExtension | 2 | Fibonacci extension levels | |
measure | 2 | A box labelled with the change over the bars it spans |
Every one of these has a tool in the on-chart drawing toolbar, so the reader can drag it, and a mouse-drawn shape reprojects, drags, serializes and reports identically to a programmatic one.
Options
| Option | Applies to | What it does |
|---|---|---|
color, width, dashArray | all | Stroke |
fill, fillOpacity | closed shapes | Interior |
locked | all | Prevents dragging, and hides the handles |
visible | all | Draw or not |
meta | all | Anything of yours, carried through the events and the saved state |
snap | all | true, or 'open', 'high', 'low', 'close': snaps points to bar values |
levels, showLabels | the fib types | Which ratios, and whether to label them |
upColor, downColor, showLabel | measure | Direction tint, and the label |
Managing drawings
| Method | Does |
|---|---|
addDrawing(config) | Adds one, returns its id |
updateDrawing(id, patch) | Patches one |
removeDrawing(id) | Removes one |
clearDrawings() | Removes all |
getDrawing(id) / getDrawings() | Reads them back |
chart.on('drawingAdded', ({ id, drawing }) => {})
chart.on('drawingUpdated', ({ id, drawing }) => {})
chart.on('drawingRemoved', ({ id }) => {})
chart.on('drawingsCleared', () => {})
getDrawings() also reports the freehand shapes made with the mouse toolbar (brush, highlighter, circle, ellipse, text). Drawings are captured by getState() and restored by setState().
Reshaping
Selecting a two-anchor drawing puts a drag handle on each anchor. Dragging one moves that end; dragging the body translates the shape with its span intact. For a measurement the distinction is load-bearing: reshaping recomputes the statistics over the bars it now spans, while translating projects the same measured move onto a different part of the chart.
locked: true shows no handles.
The freehand tools
Alongside the anchored types, the toolbar carries tools that are about marking up rather than measuring:
| Tool | Purpose |
|---|---|
| Brush | Freehand stroke that follows the cursor |
| Highlighter | Semi-transparent freehand stroke for emphasis |
| Circle / Ellipse | Region markers |
| Text | A label placed on the chart |
| Pin | Keeps a tooltip visible at a data point instead of only on hover |
| Clear | Removes every drawing at once |
Using the tools
- Shapes and lines: press, drag to size, release to commit.
- Freehand: hold and move the cursor.
- Text: place the caret and type, then confirm.
- Cancel mid-draw: press Escape. The half-drawn element is discarded and nothing is committed. A no-op if you are not mid-draw.
- Scroll to exit: using the wheel to zoom deactivates the active drawing mode, so navigating never leaves a stray shape behind.
Closed shapes take a fill color and opacity as well as a stroke; line and freehand tools use the stroke only.
Custom drawing types
ApexStock.registerDrawingTool(name, def) is the drawing-layer counterpart to registerIndicator. def.render(data, helpers) returns an SVG element built from the drawing's data-space record, with helpers supplying the projection:
ApexStock.registerDrawingTool('bracket', {
defaults: { color: '#0f766e', width: 2 },
render(data, helpers) {
const [a, b] = data.points.map((p) => helpers.dataToScreen(p.x, p.y))
const path = document.createElementNS(helpers.svgNS, 'path')
path.setAttribute('d', `M${a.x},${a.y} L${a.x},${b.y} L${b.x},${b.y}`)
path.setAttribute('stroke', data.color)
path.setAttribute('stroke-width', data.width)
path.setAttribute('fill', 'none')
return path
},
})
chart.addDrawing({ type: 'bracket', points: [{ x: x1, y: y1 }, { x: x2, y: y2 }], label: 'Q1' })
render receives the normalized record: { id, type, points, color, width, dashArray, locked, visible, meta }, plus your defaults and any extra fields you passed to addDrawing.
| Helper | Does |
|---|---|
svgNS | The SVG namespace URI, for createElementNS |
dataToScreen(x, y) | Data space to screen pixels |
screenToData(x, y) | The inverse |
getChartBounds() | The grid's bounds |
extendToBounds(p1, p2) | Extends a segment to the plot edge, as ray does |
A custom type reprojects, drags and serializes like a built-in. Registration is idempotent per name, and a built-in name is reserved unless the definition sets overwrite: true.
The registry is global and does not survive a page reload, so register your custom tools before calling setState() on a saved state that contains drawings of that type.
See also
- Measuring a Region for what the
measuretype reports - Event Markers for time-anchored flags, which are a separate layer
- Trading Overlays for draggable price lines with callbacks
- State Persistence for saving and restoring the drawing set