Demonstrates how to add Hoverable Buy/Sell Markers (annotations) and News/Dividend bullets to a React Stock Chart using SciChart.js, High Performance JavaScript Charts
drawExample.ts
index.tsx
ExampleDataProvider.ts
theme.ts
1import {
2 AnnotationHoverEventArgs,
3 AnnotationHoverModifier,
4 AUTO_COLOR,
5 CategoryAxis,
6 CustomAnnotation,
7 EAnnotationType,
8 EAxisAlignment,
9 ECoordinateMode,
10 EHorizontalAnchorPoint,
11 ELineType,
12 ENumericFormat,
13 EVerticalAnchorPoint,
14 FastCandlestickRenderableSeries,
15 FastLineRenderableSeries,
16 FastMountainRenderableSeries,
17 IAnnotation,
18 LegendModifier,
19 MouseWheelZoomModifier,
20 NativeTextAnnotation,
21 NumberRange,
22 NumericAxis,
23 OhlcDataSeries,
24 RightAlignedOuterVerticallyStackedAxisLayoutStrategy,
25 SciChartSurface,
26 SmartDateLabelProvider,
27 TextAnnotation,
28 Thickness,
29 TTargetsSelector,
30 XyDataSeries,
31 ZoomExtentsModifier,
32 ZoomPanModifier,
33} from "scichart";
34import { fetchMultiPaneData } from "../../../ExampleData/ExampleDataProvider";
35import { appTheme } from "../../../theme";
36
37// tslint:disable:no-empty
38// tslint:disable:max-line-length
39
40const getTradingData = async (startPoints?: number, maxPoints?: number) => {
41 const { dateValues, openValues, highValues, lowValues, closeValues, volumeValues } = await fetchMultiPaneData();
42
43 if (maxPoints !== undefined) {
44 return {
45 dateValues: dateValues.slice(startPoints, startPoints + maxPoints),
46 openValues: openValues.slice(startPoints, startPoints + maxPoints),
47 highValues: highValues.slice(startPoints, startPoints + maxPoints),
48 lowValues: lowValues.slice(startPoints, startPoints + maxPoints),
49 closeValues: closeValues.slice(startPoints, startPoints + maxPoints),
50 volumeValues: volumeValues.slice(startPoints, startPoints + maxPoints),
51 };
52 }
53
54 return { dateValues, openValues, highValues, lowValues, closeValues, volumeValues };
55};
56
57export const drawExample = async (rootElement: string | HTMLDivElement) => {
58 const [chart, data] = await Promise.all([
59 SciChartSurface.create(rootElement, { theme: appTheme.SciChartJsTheme }),
60 getTradingData(775, 100),
61 ]);
62 const { sciChartSurface, wasmContext } = chart;
63 const { dateValues, openValues, highValues, lowValues, closeValues } = data;
64
65 // Add an XAxis, YAxis
66 const xAxis = new CategoryAxis(wasmContext, { labelProvider: new SmartDateLabelProvider() });
67 xAxis.growBy = new NumberRange(0.01, 0.01);
68 sciChartSurface.xAxes.add(xAxis);
69 sciChartSurface.yAxes.add(
70 new NumericAxis(wasmContext, {
71 growBy: new NumberRange(0.1, 0.1),
72 labelFormat: ENumericFormat.Decimal,
73 labelPrecision: 2,
74 })
75 );
76
77 // Add a Candlestick series with some values to the chart
78
79 sciChartSurface.renderableSeries.add(
80 new FastCandlestickRenderableSeries(wasmContext, {
81 dataSeries: new OhlcDataSeries(wasmContext, {
82 xValues: dateValues,
83 openValues,
84 highValues,
85 lowValues,
86 closeValues,
87 }),
88 strokeUp: appTheme.VividSkyBlue,
89 strokeDown: appTheme.VividSkyBlue,
90 brushUp: appTheme.VividSkyBlue,
91 brushDown: "Transparent",
92 })
93 );
94
95 sciChartSurface.annotations.add(
96 new NativeTextAnnotation({
97 x1: 20,
98 y1: 20,
99 xCoordinateMode: ECoordinateMode.Pixel,
100 yCoordinateMode: ECoordinateMode.Pixel,
101 text: "Hover over the markers to see how well the random trading algorithm did.",
102 })
103 );
104
105 let position = 0;
106 let equity = 0;
107 let balance = 100;
108 let avgPrice = 0;
109
110 const positionDataSeries = new XyDataSeries(wasmContext, { dataSeriesName: "Position" });
111 const balanceDataSeries = new XyDataSeries(wasmContext, { dataSeriesName: "Balance" });
112
113 // Trade at random!
114 for (let i = 0; i < dateValues.length; i++) {
115 const low = lowValues[i];
116 const high = highValues[i];
117 const price = low + Math.random() * (high - low);
118 if (Math.random() < 0.2) {
119 const t = equity / (equity + balance);
120 if (Math.random() > t) {
121 // Buy
122 const quantity = Math.floor((Math.random() * balance) / price);
123 const size = quantity * price;
124 avgPrice = (avgPrice * position + size) / (position + quantity);
125 position += quantity;
126 balance -= size;
127 sciChartSurface.annotations.add(new TradeAnnotation(i, true, quantity, price, low, avgPrice));
128 } else {
129 // Sell
130 const quantity = Math.floor((Math.random() * equity) / price);
131 const size = quantity * price;
132 position -= quantity;
133 balance += size;
134 const pnl = (price - avgPrice) * quantity;
135 sciChartSurface.annotations.add(new TradeAnnotation(i, false, quantity, price, high, pnl));
136 }
137 }
138 equity = position * closeValues[i];
139 positionDataSeries.append(i, position);
140 balanceDataSeries.append(i, balance + equity);
141
142 // Every 25th bar, add a news bullet
143 if (i % 20 === 0) {
144 sciChartSurface.annotations.add(newsBulletAnnotation(i));
145 }
146 }
147
148 //const positionAxis = new NumericAxis(wasmContext, { id: "Position", axisAlignment: EAxisAlignment.Left });
149 const balanceAxis = new NumericAxis(wasmContext, {
150 id: "Balance",
151 //visibleRange: new NumberRange(90, 110),
152 growBy: new NumberRange(0.1, 0.1),
153 stackedAxisLength: "20%",
154 });
155 sciChartSurface.yAxes.add(balanceAxis);
156
157 sciChartSurface.annotations.add(
158 new NativeTextAnnotation({
159 x1: 20,
160 y1: 0.99,
161 xCoordinateMode: ECoordinateMode.Pixel,
162 yCoordinateMode: ECoordinateMode.Relative,
163 yAxisId: balanceAxis.id,
164 verticalAnchorPoint: EVerticalAnchorPoint.Bottom,
165 text: "Profit and Loss Curve",
166 })
167 );
168
169 sciChartSurface.layoutManager.rightOuterAxesLayoutStrategy =
170 new RightAlignedOuterVerticallyStackedAxisLayoutStrategy();
171
172 const positionSeries = new FastLineRenderableSeries(wasmContext, {
173 dataSeries: positionDataSeries,
174 stroke: AUTO_COLOR,
175 yAxisId: "Position",
176 lineType: ELineType.Digital,
177 });
178 const balanceSeries = new FastMountainRenderableSeries(wasmContext, {
179 dataSeries: balanceDataSeries,
180 stroke: appTheme.VividPurple,
181 fill: appTheme.MutedPurple,
182 yAxisId: "Balance",
183 zeroLineY: 100,
184 });
185 sciChartSurface.renderableSeries.add(balanceSeries);
186
187 const targetsSelector: TTargetsSelector<IAnnotation> = (modifer) => {
188 return modifer.getAllTargets().filter((t) => "quantity" in t);
189 };
190 sciChartSurface.chartModifiers.add(
191 new AnnotationHoverModifier({
192 targets: targetsSelector,
193 })
194 );
195
196 return { sciChartSurface, wasmContext };
197};
198
199class TradeAnnotation extends CustomAnnotation {
200 public isBuy: boolean;
201 public quantity: number;
202 public price: number;
203 public change: number;
204
205 private priceAnnotation: CustomAnnotation;
206 private toolTipAnnotation: TextAnnotation;
207
208 public onHover(args: AnnotationHoverEventArgs) {
209 const { x1, x2 } = this.getAdornerAnnotationBorders(true);
210 const viewRect = this.parentSurface.seriesViewRect;
211 if (args.isHovered && !this.priceAnnotation) {
212 this.priceAnnotation = tradePriceAnnotation(this.x1, this.price, this.isBuy);
213 this.toolTipAnnotation = new TextAnnotation({
214 yCoordShift: this.isBuy ? 20 : -20,
215 x1: this.x1,
216 y1: this.y1,
217 verticalAnchorPoint: this.isBuy ? EVerticalAnchorPoint.Top : EVerticalAnchorPoint.Bottom,
218 horizontalAnchorPoint:
219 x1 < viewRect.left + 50
220 ? EHorizontalAnchorPoint.Left
221 : x2 > viewRect.right - 50
222 ? EHorizontalAnchorPoint.Right
223 : EHorizontalAnchorPoint.Center,
224 background: this.isBuy ? appTheme.VividGreen : appTheme.VividRed,
225 textColor: "black",
226 padding: new Thickness(0, 0, 5, 0),
227 fontSize: 16,
228 text: `${this.quantity} @${this.price.toFixed(3)} ${
229 this.isBuy ? "Avg Price" : "PnL"
230 } ${this.change.toFixed(3)}`,
231 });
232 this.parentSurface.annotations.add(this.priceAnnotation, this.toolTipAnnotation);
233 } else if (this.priceAnnotation) {
234 this.parentSurface.annotations.remove(this.priceAnnotation, true);
235 this.parentSurface.annotations.remove(this.toolTipAnnotation, true);
236 this.priceAnnotation = undefined;
237 this.toolTipAnnotation = undefined;
238 }
239 }
240
241 public constructor(
242 timeStamp: number,
243 isBuy: boolean,
244 quantity: number,
245 tradePrice: number,
246 markerPrice: number,
247 change: number
248 ) {
249 super({
250 x1: timeStamp,
251 y1: markerPrice,
252 verticalAnchorPoint: isBuy ? EVerticalAnchorPoint.Top : EVerticalAnchorPoint.Bottom,
253 horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
254 });
255 this.isBuy = isBuy;
256 this.quantity = quantity;
257 this.price = tradePrice;
258 this.change = change;
259 this.onHover = this.onHover.bind(this);
260 this.hovered.subscribe(this.onHover);
261 }
262
263 public override getSvgString(annotation: CustomAnnotation): string {
264 if (this.isBuy) {
265 return `<svg id="Capa_1" xmlns="http://www.w3.org/2000/svg">
266 <g transform="translate(-54.867218,-75.091687)">
267 <path style="fill:${appTheme.VividGreen};fill-opacity:0.77;stroke:${appTheme.VividGreen};stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
268 d="m 55.47431,83.481251 c 7.158904,-7.408333 7.158904,-7.408333 7.158904,-7.408333 l 7.158906,7.408333 H 66.212668 V 94.593756 H 59.053761 V 83.481251 Z"
269 "/>
270 </g>
271 </svg>`;
272 } else {
273 return `<svg id="Capa_1" xmlns="http://www.w3.org/2000/svg">
274 <g transform="translate(-54.616083,-75.548914)">
275 <path style="fill:${appTheme.VividRed};fill-opacity:0.77;stroke:${appTheme.VividRed};stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
276 d="m 55.47431,87.025547 c 7.158904,7.408333 7.158904,7.408333 7.158904,7.408333 L 69.79212,87.025547 H 66.212668 V 75.913042 h -7.158907 v 11.112505 z"
277 />
278 </g>
279 </svg>`;
280 }
281 }
282}
283
284const tradePriceAnnotation = (timestamp: number, price: number, isBuy: boolean): CustomAnnotation => {
285 return new CustomAnnotation({
286 x1: timestamp,
287 y1: price,
288 verticalAnchorPoint: EVerticalAnchorPoint.Center,
289 horizontalAnchorPoint: EHorizontalAnchorPoint.Right,
290 svgString: `<svg xmlns="http://www.w3.org/2000/svg">
291 <path style="fill: transparent; stroke:${
292 isBuy ? appTheme.VividGreen : appTheme.VividRed
293 }; stroke-width: 3px;" d="M 0 0 L 10 10 L 0 20"></path>
294 </svg>`,
295 });
296};
297
298const newsBulletAnnotation = (x1: number): CustomAnnotation => {
299 return new CustomAnnotation({
300 x1,
301 y1: 0.99, // using YCoordinateMode.Relative and 0.99, places the annotation at the bottom of the viewport
302 yCoordinateMode: ECoordinateMode.Relative,
303 verticalAnchorPoint: EVerticalAnchorPoint.Bottom,
304 horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
305 svgString: `<svg id="Capa_1" xmlns="http://www.w3.org/2000/svg">
306 <g
307 inkscape:label="Layer 1"
308 inkscape:groupmode="layer"
309 id="layer1"
310 transform="translate(-55.430212,-77.263552)">
311 <rect
312 style="fill:${appTheme.ForegroundColor};fill-opacity:1;stroke:${appTheme.Background};stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.66666667"
313 id="rect4528"
314 width="50"
315 height="18"
316 x="55.562504"
317 y="77.395844"
318 rx="2"
319 ry="2" />
320 <text
321 xml:space="preserve"
322 style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:${appTheme.Background};fill-opacity:1;stroke:none;stroke-width:0.26458332"
323 x="59.688622"
324 y="91.160347"
325 id="text4540"><tspan
326 sodipodi:role="line"
327 id="tspan4538"
328 x="57.688622"
329 y="89.160347"
330 style=\"font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:${appTheme.Background};fill-opacity:1;stroke-width:0.26458332\">Dividend</tspan></text>
331 </g>
332 </svg>`,
333 });
334};
335The Trade Markers Example for React demonstrates how to integrate SciChart.js into a React application to render a high performance stock chart with simulated trading data. This example displays a candlestick chart with custom buy/sell markers and news bullet annotations, showcasing the power of interactive and SVG-based custom annotations.
The implementation leverages asynchronous data fetching with Promise.all() to obtain trade data and initializes the chart using the <SciChartReact/> component, as detailed in the React Charts with SciChart.js: Introducing “SciChart React” guide. The chart is set up with a CategoryAxis for dates and a NumericAxis for prices, while various renderable series (e.g., candlestick, line, and mountain series) are used to visualize market data. Custom annotations subclass the base annotation to include hover events, utilizing the AnnotationHoverModifier (see Annotation Hover API) for dynamic tooltips.
This example provides real-time simulation of trading actions with custom buy and sell markers that change appearance on hover, effectively illustrating interactive data visualization. It also demonstrates advanced multi-axis configuration and layout management, making use of techniques for integrating SVG-based annotations and setting up multiple axes as explained in the Tutorial 08 - Adding Multiple Axis documentation.
The example follows best practices for React integration by cleanly separating the chart initialization logic, passed into the <SciChartReact/> component via the initChart prop. Developers can explore asynchronous initialization workflows similar to those discussed in How to use Promise.all properly fetching data for React app and optimize performance by leveraging WebGL rendering along with efficient event handling. For further insights on performance, consider reviewing Performance Optimisation of JavaScript Applications & Charts.

Discover how to create a React Candlestick Chart or Stock Chart using SciChart.js. For high Performance JavaScript Charts, get your free demo now.

Easily create React OHLC Chart or Stock Chart using feature-rich SciChart.js chart library. Supports custom colors. Get your free trial now.

Create a React Realtime Ticking Candlestick / Stock Chart with live ticking and updating, using the high performance SciChart.js chart library. Get free demo now.

Create a React heatmap chart showing historical orderbook levels, using the high performance SciChart.js chart library. Get free demo now.

Create a React Multi-Pane Candlestick / Stock Chart with indicator panels, synchronized zooming, panning and cursors. Get your free trial of SciChart.js now.

Demonstrating the capability of SciChart.js to create a composite 2D & 3D Chart application. An example like this could be used to visualize Tenor curves in a financial setting, or other 2D/3D data combined on a single screen.

Create a React Multi-Pane Candlestick / Stock Chart with indicator panels, synchronized zooming, panning and cursors. Get your free trial of SciChart.js now.

Create a React Depth Chart, using the high performance SciChart.js chart library. Get free demo now.

This demo shows you how to create a <strong>{frameworkName} User Annotated Stock Chart</strong> using SciChart.js. Custom modifiers allow you to add lines and markers, then use the built in serialisation functions to save and reload the chart, including the data and all your custom annotations.