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

TypeAliasesPointsDraws
trendlineline2A straight line between two anchors
ray2A line from the first anchor through the second, extended
horizontalLinehline1A price level, from the first point's y
verticalLinevline1A time marker, from the first point's x
rectanglezone2A price and time box
fibRetracement2Fibonacci levels between two anchor prices
fibExtension2Fibonacci extension levels
measure2A 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

OptionApplies toWhat it does
color, width, dashArrayallStroke
fill, fillOpacityclosed shapesInterior
lockedallPrevents dragging, and hides the handles
visibleallDraw or not
metaallAnything of yours, carried through the events and the saved state
snapalltrue, or 'open', 'high', 'low', 'close': snaps points to bar values
levels, showLabelsthe fib typesWhich ratios, and whether to label them
upColor, downColor, showLabelmeasureDirection tint, and the label

Managing drawings

MethodDoes
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:

ToolPurpose
BrushFreehand stroke that follows the cursor
HighlighterSemi-transparent freehand stroke for emphasis
Circle / EllipseRegion markers
TextA label placed on the chart
PinKeeps a tooltip visible at a data point instead of only on hover
ClearRemoves 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.

HelperDoes
svgNSThe 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