Skip to main content

Editing Drawings

Users edit a drawing by selecting it: they can drag it, or its points, and change its look in the quick-edit toolbar and the full editor. Your code can edit drawings too, and follow what users do through each drawing's events. This page covers all of these.

Selecting and Moving Drawings​

A drawing with isEditable: true can be selected: drawings made with the drawing tools always are, and set it on drawings you add in code. Click a drawing to select it, or call drawing.select(item). A selected drawing shows its points as handles:

  • drag a handle to move that point;
  • drag the drawing to move it all;
  • click elsewhere, or call drawing.clearSelection(), to deselect it.

On a touch screen, a long press selects a drawing; see Keyboard and Touch. With the keyboard, N and P step through the drawings, and the arrow keys nudge the selected one.

The Quick-Edit Toolbar​

A selected drawing gets a floating toolbar on the chart, following it as the chart pans and zooms. DrawingToolsController adds it. From left to right, it has:

  • a drag handle, to move the toolbar out of the way;
  • a colour picker, for drawings with one stroke colour;
  • a thickness stepper, − and +;
  • delete, which removes the drawing;
  • ⋯, which opens the full editor. It appears only when the controller has an editorContainer.

Its look is .st-inline-toolbar in scitrader.css.

The Full Editor​

The full editor shows every option of the selected drawing: its colours, thicknesses, label settings, Fibonacci levels and so on, each applied as it is changed. To offer it, pass an element to mount it in as editorContainer:

const drawing = DrawingToolsController.attach(financeChartPanel.financeApi, {
editorContainer: document.getElementById("editor"), // where the full editor is mounted
// Called as the full editor opens, from the quick-edit toolbar's ⋯ or openEditor: e.g. to show its panel
onOpenFullEditor: (annotation) => editorPanel.classList.add("open"),
editorRootClassName: "docked-editor", // a class for the editor's root element
editorPanelClassName: "docked-editor-panel", // a class for its panel
});

drawing.openEditor(item); // select a drawing and open the full editor for it, e.g. from an Edit button
Docking the editor

The editor sets its own position and look inline, as a floating panel near the top right of the page, even with an editorContainer. To dock it in its container instead, give it classes with editorRootClassName and editorPanelClassName, and override its position with !important, as below.

/* The editor's root: fill its container rather than float */
.docked-editor {
position: static !important;
inset: auto !important;
width: 100% !important;
height: 100% !important;
overflow-y: auto;
}
/* The editor's panel: full width, without the floating panel's corners and shadow */
.docked-editor-panel {
width: 100% !important;
max-height: 100% !important;
box-sizing: border-box;
border-radius: 0 !important;
box-shadow: none !important;
}

The examples below do this, with the editor in a side panel under the Drawing List, whose Edit buttons call openEditor.

Editing Drawings in Code​

A drawing is an annotation, so you edit it by setting its properties. Find it in drawing.drawings, or keep the annotation you created:

import { Point } from "scichart";
import { FibonacciRetracementAnnotation } from "scichart-financial-tools";

const fib = drawing.drawings.find((item) => item.type === "FibonacciRetracementAnnotation")
?.annotation as FibonacciRetracementAnnotation;

fib.points = [new Point(startTime, startPrice), new Point(endTime, endPrice)]; // move its points
fib.thresholds = [0, 0.382, 0.5, 0.618, 1]; // its own options: here, its levels
fib.isEditable = false; // lock it: the user can no longer select or move it
fib.isHidden = true; // hide it, without removing it

Each tool's options are listed on its page under Add Drawing Tools, and the catalogue gives every tool's annotation class.

Selection and Edit Events​

DrawingToolsController raises an event when a drawing is added or removed (drawingsChanged). Selection and edits are raised by each drawing's annotation: SciChart's annotation events.

EventRaised whenPayload
selectedChangedThe drawing is selected or deselectedtrue when selected
dragStartedThe user starts dragging the drawing or one of its points
dragDeltaDuring the dragThe movement, as AnnotationDragDeltaEventArgs
dragEndedThe user has moved the drawing or one of its points
clickedThe drawing is clicked, whether or not it is editableAnnotationClickEventArgs
hoveredThe mouse moves over the drawingAnnotationHoverEventArgs

To follow every drawing, subscribe to each new one as drawingsChanged reports it. IDrawingItem.annotation is typed as SciChart's IAnnotation, which doesn't declare the events; they are on AnnotationBase, which every drawing extends:

import { AnnotationBase, IAnnotation } from "scichart";

const watched = new Set<IAnnotation>();
drawing.drawingsChanged.subscribe((items) =>
items
.filter((item) => !watched.has(item.annotation))
.forEach(({ annotation }) => {
watched.add(annotation);
const events = annotation as AnnotationBase;
events.selectedChanged.subscribe((selected) => console.log(annotation.type, selected ? "selected" : "deselected"));
events.dragEnded.subscribe(() => console.log(annotation.type, "moved"));
})
);

For a drawing you create in code, you can pass the same as callbacks in its options instead: onDragStarted, onDrag, onDragEnded, onClick and onHover.

note

Edits made in the quick-edit toolbar or the full editor, such as a new colour or thickness, change the annotation's properties without raising any event. To save a drawing's look after an edit, read its properties when you need them, e.g. with financeChartPanel.toJSON().

The annotation events and callback options, from scichart's AnnotationBase, with their TSDoc
// On every drawing's annotation (SciChart's AnnotationBase)
selectedChanged: EventHandler<boolean>;
dragStarted: EventHandler<void>;
dragDelta: EventHandler<AnnotationDragDeltaEventArgs>;
dragEnded: EventHandler<void>;
clicked: EventHandler<AnnotationClickEventArgs>;
hovered: EventHandler<AnnotationHoverEventArgs>;

// The same as callbacks, in the options of an annotation you create (IAnnotationBaseOptions)
/**
* Callback function called when drag has started. Only applicable if {@link isEditable} is true
*/
onDragStarted?: (() => void) | string;
/**
* Callback function called when drag has ended. Only applicable if {@link isEditable} is true
*/
onDragEnded?: (() => void) | string;
/**
* Callback function called when drag operation is in progress. Only applicable if {@link isEditable} is true
*/
onDrag?: ((args: AnnotationDragDeltaEventArgs) => void) | string;
/**
* Callback function called when the annotation is clicked. Fires even for non Editable annotations
*/
onClick?: ((args: AnnotationClickEventArgs) => void) | string;
/**
* Callback function called when the annotation is hovered. Fires even for non Editable annotations
*/
onHover?: ((args: AnnotationHoverEventArgs) => void) | string;

TypeScript Example: Editing Drawings with the Full Editor and Events​

A BTC-USD chart from Coinbase with a Fibonacci retracement to edit. Click it to select it: the quick-edit toolbar appears on it, and the line at the bottom follows its selectedChanged and dragEnded events. Drag it or its points. Press the pencil in the list on the right to open the full editor below the list. Draw more with the rail at the top.

The code for this example is found below.

import "scichart-scitrader/scitrader.css";
import { AnnotationBase, IAnnotation, Point } from "scichart";
import { FibonacciRetracementAnnotation } from "scichart-financial-tools";
import {
CandleInterval,
CoinbaseExchange,
DEFAULT_FEED_ID,
defaultExchangeProvider,
DRAWING_TOOL_BY_ID,
DrawingList,
DrawingToolbar,
DrawingToolsController,
EDataSourceStringId,
idbPriceStore,
IDrawingItem,
OHLCStudy,
PaneId,
registerDrawingTools,
SciFinanceChart,
SciFinanceChartPanel,
VolumeStudy,
} from "scichart-scitrader";

// This example expects this HTML:
// <div style="display: flex; flex-direction: column; height: 100%">
// <div id="tools"></div>
// <div style="display: flex; flex: 1; min-height: 0">
// <div id="chart" style="flex: 1; min-width: 0"></div>
// <div style="display: flex; flex-direction: column; width: 320px">
// <div id="drawings" style="max-height: 40%; overflow-y: auto"></div>
// <div id="editor" style="flex: 1; min-height: 0; overflow-y: auto"></div>
// </div>
// </div>
// <div id="log"></div>
// </div>

// Once, at app startup: register a data source, and the drawing tools' annotation types
defaultExchangeProvider.registerExchange(CoinbaseExchange, idbPriceStore);
registerDrawingTools();

// The full editor sets its own position and look inline, as a floating panel. These rules dock it in
// its container instead: give the editor these classes with editorRootClassName and editorPanelClassName
const EDITOR_CSS = `
.docked-editor { position: static !important; inset: auto !important; width: 100% !important; height: 100% !important; overflow-y: auto; }
.docked-editor-panel { width: 100% !important; max-height: 100% !important; box-sizing: border-box; border-radius: 0 !important; box-shadow: none !important; }
`;

async function createChart() {
const log = document.getElementById("log");
Object.assign(log.style, logStyle);
const say = (text: string) => (log.textContent = text);

const financeChartPanel = await SciFinanceChartPanel.create(document.getElementById("chart"), {
exchangeName: CoinbaseExchange.name, // name="Coinbase" matches the built-in CoinbaseExchange
symbol: "BTC-USD",
interval: CandleInterval.SIX_HOURS,
period: "3M",
studies: [new OHLCStudy(), new VolumeStudy()],
});
const chart = financeChartPanel.financeApi;

// A drawing to edit: a Fibonacci retracement of the last 90 candles' move
const candles = getCandles(chart);
const n = candles.times.length;
const low = lowest(candles, n - 90, n);
const high = highest(candles, n - 90, n);
chart.chartPanes
.get(PaneId.DEFAULT)
.surface.annotations.add(
new FibonacciRetracementAnnotation({ points: low.x < high.x ? [low, high] : [high, low], isEditable: true })
);

// Editing a drawing. Click a drawing to select it: the quick-edit toolbar appears on it, with its
// colour, thickness, line style, a delete button and a "more" button. "More" opens the full editor,
// every option of the drawing, in editorContainer. Without editorContainer there is no "more" button
document.head.append(Object.assign(document.createElement("style"), { textContent: EDITOR_CSS }));
const drawing = DrawingToolsController.attach(chart, {
editorContainer: document.getElementById("editor"),
editorRootClassName: "docked-editor",
editorPanelClassName: "docked-editor-panel",
// Called as the full editor opens, e.g. to show the panel that holds it
onOpenFullEditor: (annotation) => say(`Full editor opened for ${nameOf(annotation)}`),
});

// The drawing tools rail, to draw more
const toolbar = DrawingToolbar.create(document.getElementById("tools"), drawing, { orientation: "horizontal" });

// The drawing list: clicking a row selects the drawing (select), and its edit button opens the
// full editor for it (openEditor), as the quick-edit toolbar's "more" button does
const list = DrawingList.create(document.getElementById("drawings"), {
controller: drawing,
title: "Drawings",
onEdit: (id) => {
const item = drawing.drawings.find((d) => d.id === id);
if (item) drawing.openEditor(item);
},
});

// Events for selection and edits come from each drawing's annotation (SciChart's IAnnotation):
// selectedChanged when it is selected or deselected, dragEnded when the user has moved it or one
// of its points. Subscribe to each new drawing as drawingsChanged reports it
const watched = new Set<IAnnotation>();
const watch = (items: readonly IDrawingItem[]) =>
items
.filter((item) => !watched.has(item.annotation))
.forEach(({ annotation }) => {
watched.add(annotation);
// The events are on AnnotationBase, which every drawing extends; `annotation` is typed as IAnnotation
const events = annotation as AnnotationBase;
events.selectedChanged.subscribe((selected) =>
say(`${nameOf(annotation)} ${selected ? "selected" : "deselected"}`)
);
events.dragEnded.subscribe(() => say(`${nameOf(annotation)} moved`));
});
drawing.drawingsChanged.subscribe((items) => {
say(`${items.length} drawing(s) on the chart`);
watch(items);
});
watch(drawing.drawings);
say("Click the Fibonacci retracement to select it, or press Edit in the list");

// When the page goes away: list.destroy(); toolbar.destroy(); drawing.detach(); financeChartPanel.cleanup();
return { financeChartPanel, drawing, list, toolbar };
}

// A drawing's tool name, e.g. "Fibonacci Retracement"
const nameOf = (annotation: IAnnotation) => DRAWING_TOOL_BY_ID.get(annotation.type)?.label ?? annotation.type;
// ---------------------------------------------------------------------------------------------
// Not part of the SDK: helpers that read the loaded candles, to choose where to place drawings
// ---------------------------------------------------------------------------------------------

// The loaded candles: times in seconds (the x values of a SciTrader chart), and prices
function getCandles(chart: SciFinanceChart) {
const dm = chart.dataManager;
const values = (name: EDataSourceStringId) => dm.getValues<number>(dm.getSourceIdByName(DEFAULT_FEED_ID, name));
return {
times: dm.xValues,
highs: values(EDataSourceStringId.HIGH),
lows: values(EDataSourceStringId.LOW),
closes: values(EDataSourceStringId.CLOSE),
};
}
type TCandles = ReturnType<typeof getCandles>;

// The lowest low, or highest high, of candles [from, to), as a point on the chart
function lowest(c: TCandles, from: number, to: number): Point {
let i = from;
for (let j = from; j < to; j++) if (c.lows[j] < c.lows[i]) i = j;
return new Point(c.times[i], c.lows[i]);
}
function highest(c: TCandles, from: number, to: number): Point {
let i = from;
for (let j = from; j < to; j++) if (c.highs[j] > c.highs[i]) i = j;
return new Point(c.times[i], c.highs[i]);
}

const logStyle: Partial<CSSStyleDeclaration> = {
padding: "8px 12px",
background: "var(--st-ui-background, #2b2839)",
borderTop: "1px solid var(--st-ui-divider, rgba(255,255,255,0.1))",
color: "var(--st-ui-text-muted, #9da2bc)",
fontSize: "13px",
};

createChart();

React Example: Editing Drawings with the Full Editor and Events​

The same chart in React. The editor's element is rendered with the component, and the events are subscribed in an effect, which unsubscribes when the drawings change.

The code for this example is found below.

import "scichart-scitrader/scitrader.css";
import * as React from "react";
import { AnnotationBase, IAnnotation, Point } from "scichart";
import { FibonacciRetracementAnnotation } from "scichart-financial-tools";
import {
CandleInterval,
CoinbaseExchange,
DEFAULT_FEED_ID,
defaultExchangeProvider,
DRAWING_TOOL_BY_ID,
EDataSourceStringId,
idbPriceStore,
ISciFinanceChartPanelApi,
ISciFinanceChartPanelConfig,
OHLCStudy,
PaneId,
registerDrawingTools,
SciFinanceChart,
VolumeStudy,
} from "scichart-scitrader";
import { DrawingList, DrawingToolbar, SciFinanceChartView, useDrawingTools } from "scichart-scitrader-react";

// Once, at app startup: register a data source, and the drawing tools' annotation types
defaultExchangeProvider.registerExchange(CoinbaseExchange, idbPriceStore);
registerDrawingTools();

// The full editor sets its own position and look inline, as a floating panel. These rules dock it in
// its container instead: give the editor these classes with editorRootClassName and editorPanelClassName
const EDITOR_CSS = `
.docked-editor { position: static !important; inset: auto !important; width: 100% !important; height: 100% !important; overflow-y: auto; }
.docked-editor-panel { width: 100% !important; max-height: 100% !important; box-sizing: border-box; border-radius: 0 !important; box-shadow: none !important; }
`;

// A drawing's tool name, e.g. "Fibonacci Retracement"
const nameOf = (annotation: IAnnotation) => DRAWING_TOOL_BY_ID.get(annotation.type)?.label ?? annotation.type;

export function EditingDrawingsChart() {
const [chartApi, setChartApi] = React.useState<ISciFinanceChartPanelApi>();
const [message, setMessage] = React.useState("Click the Fibonacci retracement to select it, or press Edit in the list");
// The element the full editor is mounted in. useDrawingTools reads its options once, when it
// attaches the controller, so render the editor's element before the chart is ready
const editorRef = React.useRef<HTMLDivElement>(null);
const options = React.useMemo(
() => ({
// "More" on the quick-edit toolbar opens the full editor, every option of the drawing, here
get editorContainer() {
return editorRef.current ?? undefined;
},
editorRootClassName: "docked-editor",
editorPanelClassName: "docked-editor-panel",
// Called as the full editor opens, e.g. to show the panel that holds it
onOpenFullEditor: (annotation: IAnnotation) => setMessage(`Full editor opened for ${nameOf(annotation)}`),
}),
[]
);
const { controller, drawings } = useDrawingTools(chartApi, options);

// Create the config once per mounted chart: studies are live objects bound to one chart
const config = React.useMemo<ISciFinanceChartPanelConfig>(
() => ({
exchangeName: CoinbaseExchange.name, // name="Coinbase" matches the built-in CoinbaseExchange
symbol: "BTC-USD",
interval: CandleInterval.SIX_HOURS,
period: "3M",
studies: [new OHLCStudy(), new VolumeStudy()],
}),
[]
);

// A drawing to edit: a Fibonacci retracement of the last 90 candles' move
const onInit = (api: ISciFinanceChartPanelApi) => {
const chart = api.financeApi;
const candles = getCandles(chart);
const n = candles.times.length;
const low = lowest(candles, n - 90, n);
const high = highest(candles, n - 90, n);
chart.chartPanes
.get(PaneId.DEFAULT)
.surface.annotations.add(
new FibonacciRetracementAnnotation({ points: low.x < high.x ? [low, high] : [high, low], isEditable: true })
);
setChartApi(api);
};

// Events for selection and edits come from each drawing's annotation (SciChart's IAnnotation):
// selectedChanged when it is selected or deselected, dragEnded when the user has moved it or one
// of its points. Subscribe to each drawing, and unsubscribe when it goes
React.useEffect(() => {
const unsubscribe = drawings.map(({ annotation }) => {
const onSelected = (selected: boolean) => setMessage(`${nameOf(annotation)} ${selected ? "selected" : "deselected"}`);
const onMoved = () => setMessage(`${nameOf(annotation)} moved`);
// The events are on AnnotationBase, which every drawing extends; `annotation` is typed as IAnnotation
const events = annotation as AnnotationBase;
events.selectedChanged.subscribe(onSelected);
events.dragEnded.subscribe(onMoved);
return () => {
events.selectedChanged.unsubscribe(onSelected);
events.dragEnded.unsubscribe(onMoved);
};
});
return () => unsubscribe.forEach((u) => u());
}, [drawings]);

// Fills its parent, so give the parent a height
return (
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
<style>{EDITOR_CSS}</style>
<DrawingToolbar controller={controller} orientation="horizontal" />
<div style={{ display: "flex", flex: 1, minHeight: 0 }}>
<div style={{ flex: 1, minWidth: 0 }}>
<SciFinanceChartView config={config} onInit={onInit} />
</div>
<div style={{ display: "flex", flexDirection: "column", width: 320 }}>
{/* Clicking a row selects the drawing; its edit button opens the full editor for it */}
<DrawingList
controller={controller}
title="Drawings"
onEdit={(id) => {
const item = drawings.find((d) => d.id === id);
if (item) controller.openEditor(item);
}}
style={{ maxHeight: "40%", overflowY: "auto" }}
/>
<div ref={editorRef} style={{ flex: 1, minHeight: 0, overflowY: "auto" }} />
</div>
</div>
<div style={logStyle}>{message}</div>
</div>
);
}
// ---------------------------------------------------------------------------------------------
// Not part of the SDK: helpers that read the loaded candles, to choose where to place drawings
// ---------------------------------------------------------------------------------------------

// The loaded candles: times in seconds (the x values of a SciTrader chart), and prices
function getCandles(chart: SciFinanceChart) {
const dm = chart.dataManager;
const values = (name: EDataSourceStringId) => dm.getValues<number>(dm.getSourceIdByName(DEFAULT_FEED_ID, name));
return {
times: dm.xValues,
highs: values(EDataSourceStringId.HIGH),
lows: values(EDataSourceStringId.LOW),
closes: values(EDataSourceStringId.CLOSE),
};
}
type TCandles = ReturnType<typeof getCandles>;

// The lowest low, or highest high, of candles [from, to), as a point on the chart
function lowest(c: TCandles, from: number, to: number): Point {
let i = from;
for (let j = from; j < to; j++) if (c.lows[j] < c.lows[i]) i = j;
return new Point(c.times[i], c.lows[i]);
}
function highest(c: TCandles, from: number, to: number): Point {
let i = from;
for (let j = from; j < to; j++) if (c.highs[j] > c.highs[i]) i = j;
return new Point(c.times[i], c.highs[i]);
}

const logStyle: React.CSSProperties = {
padding: "8px 12px",
background: "var(--st-ui-background, #2b2839)",
borderTop: "1px solid var(--st-ui-divider, rgba(255,255,255,0.1))",
color: "var(--st-ui-text-muted, #9da2bc)",
fontSize: 13,
};

Drawing Colours and Themes​

Drawings follow the chart's theme wherever they have no colour of their own: a line drawn with a tool that sets no colour takes the theme's drawing line colour, and changes with setTheme. A colour set by the tool, in your code, or in the editor is kept. The chart does this with applyDrawingTheme, as drawings are placed or restored and on every theme change. The theme's drawing colours are listed under How Drawings Work.

The applyDrawingTheme function, with its TSDoc
/**
* Give a drawing `to`'s colours wherever it still has a default colour: one its tool left unset,
* or one some theme put there (a built-in theme's, or `from`'s, the theme being replaced). Colours
* set in the drawing's editor, and the colours tools choose for themselves, are left alone.
*
* `SciFinanceChart` calls this as drawings are placed or restored, and for every drawing on
* `setTheme`.
*/
function applyDrawingTheme(drawing: unknown, to: ISciTraderDrawingColors, from?: ISciTraderDrawingColors): void;

Styling the Quick-Edit Toolbar​

ClassElement
.st-inline-toolbarThe toolbar
.st-inline-toolbar__handleThe drag handle
.st-inline-toolbar__swatchThe colour picker's swatch
.st-inline-toolbar__valueThe thickness value
.st-inline-toolbar__buttonThe thickness, delete and ⋯ buttons
.st-inline-toolbar__separatorThe dividers between groups
/* A larger, square quick-edit toolbar */
.st-inline-toolbar { border-radius: 0; padding: 6px; } /* square corners, more space */
.st-inline-toolbar__button { width: 32px; height: 32px; } /* bigger buttons */
The default quick-edit toolbar CSS (.st-inline-toolbar), from scitrader.css
.st-inline-toolbar {
gap: 4px;
padding: 6px 8px;
border-radius: 6px;
background: var(--st-ui-menu-background, rgba(20,20,30,0.95));
border: 1px solid rgb(var(--st-ui-overlay-rgb, 255 255 255) / 0.08);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}

.st-inline-toolbar__button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 4px;
border: none;
border-radius: 50%;
background: transparent;
color: var(--st-ui-icon, #e5e7eb);
cursor: pointer;
}

.st-inline-toolbar__button:hover {
color: var(--st-ui-text, #ffffff);
background: rgb(var(--st-ui-overlay-rgb, 255 255 255) / 0.08);
}

.st-inline-toolbar__handle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 22px;
color: rgba(229, 231, 235, 0.55);
cursor: grab;
}

.st-inline-toolbar__handle:hover {
color: var(--st-ui-text, #ffffff);
}

.st-inline-toolbar__separator {
width: 1px;
height: 18px;
margin: 0 4px;
background: rgb(var(--st-ui-overlay-rgb, 255 255 255) / 0.12);
}

.st-inline-toolbar__swatch {
align-items: center;
justify-content: center;
padding: 0;
border: 1px solid rgb(var(--st-ui-overlay-rgb, 255 255 255) / 0.25);
border-radius: 4px;
cursor: pointer;
}

.st-inline-toolbar__swatch:hover {
border-color: rgb(var(--st-ui-overlay-rgb, 255 255 255) / 0.45);
}

.st-inline-toolbar__value {
min-width: 24px;
font-size: 12px;
text-align: center;
color: var(--st-ui-icon, #e5e7eb);
}
The InlineAnnotationToolbar class, with its TSDoc
/**
* TradingView-style quick-edit strip rendered as a floating toolbar over the
* chart. Color picker (native), thickness stepper, delete, and a "⋯" (Tune)
* button that opens the full editor. Appends itself to `container` on
* construction; {@link destroy} removes it.
*/
class InlineAnnotationToolbar {
constructor(container: HTMLElement, options: IInlineAnnotationToolbarOptions);
/** Remove the toolbar from the DOM. */
destroy(): void;
}