Angular Chart Hoverable Buy Sell Marker Annotations

Demonstrates how to add Hoverable Buy/Sell Markers (annotations) and News/Dividend bullets to a Angular Stock Chart using SciChart.js, High Performance JavaScript Charts

Fullscreen

Edit

 Edit

Docs

drawExample.ts

angular.ts

ExampleDataProvider.ts

theme.ts

Copy to clipboard
Minimise
Fullscreen
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};
335

Angular Chart Hoverable Buy Sell Marker Annotations

Overview

This example, "Trade Markers", demonstrates how to integrate SciChart.js within an Angular standalone component to render a high performance stock chart featuring interactive buy/sell markers and news/dividend bullet annotations. The implementation leverages the scichart-angular package to facilitate seamless integration within Angular applications, ensuring that all chart initialization and interactions are encapsulated in a dedicated Angular component.

Technical Implementation

The chart is initialized asynchronously using async/await together with Promise.all() to fetch trading data and set up the SciChartSurface efficiently. A CategoryAxis is used for date values and NumericAxis represent price and balance information, while custom SVG-based annotations are created for buy and sell trades. Custom event handlers, particularly through the use of the AnnotationHoverModifier (see Annotation Hover API), allow annotations to respond to hover events by displaying additional tooltips. This approach mirrors common asynchronous integration strategies in Angular, as discussed in resources like async/await in Angular ngOnInit.

Features and Capabilities

The example provides real-time simulation of trading actions where markers are dynamically added to the chart. It demonstrates advanced features such as a multi-axis layout where a secondary axis displays profit and loss curves, a capability further detailed in the Tutorial 08 - Adding Multiple Axis documentation. Custom SVG annotations enable distinct visual representations for buy (green) and sell (red) actions, and interactive tooltips enhance user engagement by presenting detailed trade information on hover.

Integration and Best Practices

The integration follows Angular best practices by encapsulating the chart within a standalone component using the ScichartAngularComponent. The chart’s asynchronous initialization and the clear separation of concerns promote maintainability and reusability in large Angular applications. Performance optimizations are achieved through efficient WebGL rendering, ensuring that high-frequency data updates do not impact the user experience. Developers looking to extend or customize this approach should refer to the Getting Started with SciChart JS guide for foundational tips and consult the Annotation Hover Documentation for advanced interactive customization strategies.

angular Chart Examples & Demos

See Also: Financial Charts (9 Demos)

Angular Candlestick Chart | Online JavaScript Chart Examples

Angular Candlestick Chart

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

Angular OHLC Chart | Angular Charts | SciChart.js Demo

Angular OHLC Chart

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

Angular Realtime Ticking Stock Chart | SciChart.js Demo

Angular Realtime Ticking Stock Charts

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

NEW!
Angular Orderbook Heatmap | Angular Charts | SciChart.js Demo

Angular Orderbook Heatmap

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

Angular Multi-Pane Stock Chart using Subcharts | View JavaScript Charts

Angular Multi-Pane Stock Charts using Subcharts

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

Tenor Curves Demo | Angular Charts | SciChart.js Demo

Tenor Curves Demo

Demonstrating the capability of SciChart.js to create a composite 2D &amp; 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.

Angular Multi-Pane Stock Chart | View JavaScript Charts

Angular Multi-Pane Stock Charts using Sync Multi-Chart

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

Angular Market Depth Chart | Angular Charts | SciChart.js

Angular Market Depth Chart

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

Angular User Annotated Stock Chart | SciChart.js Demo

Angular User Annotated Stock Chart

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.

SciChart Ltd, 16 Beaufort Court, Admirals Way, Docklands, London, E14 9XL.