UNPKG

ngx-lightweight-charts

Version:
517 lines (393 loc) 14.8 kB
# ngx-lightweight-charts An easily extendable Angular wrapper for Trading View lightweight-charts ### What it is. A wrapper that exposes core chart functionality within an Angular app, and allows new functionality to be easily added. ### What it's not. A (re)implementation of the entire `lightweight-charts` [api][11]. ### How to use. --- 1) [Getting started](#1) 2) [Displaying a chart](#2) 3) [Common chart inputs and outputs](#3) 4) [TVChart - accessing the underlying IChartAPI and ISeriesApi instance](#4) 5) [Displaying custom data](#5) 6) [Implemented behaviour](#6) 7) [Adding behaviour](#7) --- # 1. ### Getting started | Version | Angular version | |:--------|---------------| | `0.3.x` | 18 | | `0.2.x` | 17 | Installation ```bash npm i ngx-lightweight-charts ``` Add providers ```ts import { ApplicationConfig } from '@angular/core'; import { getTVChartDefaultProviders } from "ngx-lightweight-charts"; export const appConfig: ApplicationConfig = { providers: [ // ... getTVChartDefaultProviders() ] }; ``` # 2. ### Displaying a chart There are two ways: Either use the `tvChart` directive, specifying the type of chart to be displayed. ```html <div tvChart="Line" [data]="lineData"></div> ``` Or use one of the following convenience components that wrap `tvChart` to create a specific chart type. ```html <tv-area-chart [data]="pointData"></tv-area-chart> ``` ```html <tv-bar-chart [data]="barData"></tv-bar-chart> ``` ```html <tv-baseline-chart [data]="pointData"></tv-baseline-chart> ``` ```html <tv-candlestick-chart [data]="klineData"></tv-candlestick-chart> ``` ```html <tv-histogram-chart [data]="pointData"></tv-histogram-chart> ``` ```html <tv-line-chart [data]="pointData"></tv-line-chart> ``` # 3. ### Common chart inputs and outputs All charts expose the following signal based inputs and outputs: (Generic type `T` extends [SeriesType][9] and `HorzItemScale` defaults to [Time][15]) | Input | type | |:---------------------------|:------------------------------------------------| | id | string | | options | DeepPartial\<[ChartOptions][1]> | | seriesOptions | [SeriesPartialOptionsMap][2][T] | | data | [SeriesDataItemTypeMap][3]\<HorzScaleItem>[T][] | | markers | [SeriesMarker][4]\<HorzScaleItem>[] | <br/> | Output | type | |:---------------------------|:--------------------------------------| | initialised | TVChart<T, HorzScaleItem> | | chartClick | [MouseEventParams][5]\<HorzScaleItem> | | chartDBLClick | [MouseEventParams][5]\<HorzScaleItem> | | crosshairMoved | [MouseEventParams][5]\<HorzScaleItem> | | visibleTimeRangeChanged | [Range][6]\<HorzScaleItem> | | visibleLogicalRangeChanged | [LogicalRange][7] \| null | | sizeChanged | number | | dataChanged | [DataChangedScope][8] | # 4. ### TVChart - accessing the underlying [IChartAPI][10] and [ISeriesApi][12] instance `TVChart` is the core class that creates, manages and exposes a trading view [chart][10] and its associated [series][12]. For convenience `TVChart` implements the majority of the `IChartApi` interface and also exposes the `IChartApi`, `ITimeScaleApi` and `ISeriesApi` subscriptions as RXJS streams. It also exposes the underlying [chart][10], [timeScale][13], [priceScale][14] and [series][12] through accessors. --- Every chart directive/component is simply a container that initialises an injected `TVChart` instance and exposes a limited set of inputs and outputs to interact with the core functionality of the chart and series. Once a `TVChart` has been initialised, there are 2 ways to access it: 1). Through the `initialised` output of the chart directive/component ```ts import {Component} from "@angular/core"; import {TVChartDirective, TVChart} from "ngx-lightweight-chart"; import {LineData} from "lightweight-charts"; @Component({ selector: 'my-component', standalone: true, imports: [ TVChartDirective ], template: ` <div tvChart="Line" [data]="chartData" (initialised)="onChartInit($event)"></div> ` }) export class MyComponent { chartData: LineData[] onChartInit(chart: TVChart<"Line">) { //... perform some action through the TVChart API } } ``` 2). Through the `tvChartCollector` directive when creating reusable functionality, which can be used to access and interact with a single `TVChart` or a collection. The `tvChartCollector` also ensures that all `TVChart` instances have been fully initialised before exposing them for access through the `charts` signal. Accessing a single `TVChart` instance: ```html <div tvChart="Line" [data]="chartData" tvChartCollector myDirective></div> ``` ```ts import {Directive, effect, inject} from "@angular/core"; import {TVChartCollectorDirective, TVChart} from "ngx-lightweight-charts"; @Directive({ selector: '[myDirective]', standalone: true }) export class MyDirective { readonly #collector = inject(TVChartCollectorDirective); constructor() { effect(() => { this.#collector.charts()?.forEach((chart: TVChart<any>) => { //... perform some action through the TVChart API }); }); } } ``` Accessing multiple TVChart instances: ```html <div tvChartCollector myMultiChartDirective> <tv-candlestick-chart [data]="klineData"></tv-candlestick-chart> <tv-histogram-chart [data]="pointData"></tv-histogram-chart> <tv-line-chart [data]="pointData"></tv-line-chart> </div> ``` ```ts import {Directive, effect, inject} from "@angular/core"; import {TVChartCollectorDirective, TVChart} from "ngx-lightweight-charts"; @Directive({ selector: '[myMultiChartDirective]', standalone: true }) export class MyMultiChartDirective { readonly #collector = inject(TVChartCollectorDirective); constructor() { effect(() => { this.#collector.charts()?.forEach((chart: TVChart<any>) => { //... perform some action through the TVChart API }); }); } } ``` You may have noticed that the implementation of `MyDirective` and `MyMultiChartDirective` are identical. This is intentional. The `TVChartCollectorDirective.charts` signal always returns an array of charts (whether collecting a single or multiple) allowing the flexibility to easily implement directives or components that work with single and/or multiple charts. The `tvChartCollector` also accepts an array of id's to facilitate the filtering of charts by id: ```html <div [tvChartCollector]="['one, 'two']" myDirective> <tv-candlestick-chart id="one" [data]="klineData"></tv-candlestick-chart> <tv-histogram-chart id="two" [data]="pointData"></tv-histogram-chart> <tv-line-chart [data]="pointData"></tv-line-chart> </div> ``` ```ts import {Directive, effect, inject} from "@angular/core"; import {TVChartCollectorDirective, TVChart} from "ngx-lightweight-charts"; @Directive({ selector: '[myDirective]', standalone: true }) export class MyDirective { readonly #collector = inject(TVChartCollectorDirective); constructor() { effect(() => { this.#collector.charts()?.forEach((chart: TVChart<any>) => { //... perform something only on chart "one" and "two" }); }); } } ``` # 5. ### Displaying custom data The following example uses the [Custom chart HLC area][16] implementation - source code can be found [here][17] Given the following app component: ```ts import {Component} from "@angular/core"; @Component({ selector: 'app-root', standalone: true, templateUrl: './app.component.html' }) export class AppComponent { customSeriesView = new HLCAreaSeries(); customData = generateAlternativeCandleData(100); } ``` There are 2 ways to display custom data: 1). Using the `TVChartCustomSeriesComponent` ```html <tv-custom-series-chart [data]="customData" [customSeriesView]="customSeriesView"></tv-custom-series-chart> ``` ![Custom series!](/assets/custom-series.png "Displaying a custom series") 2). By adding an additional (custom) series to an existing chart ```html <div tvChart="Candlestick" [data]="customData" tvChartCollector [customSeriesExample]="customSeriesView"></div> ``` ```ts import {Directive, effect, inject, input} from "@angular/core"; import {TVChartCollectorDirective, TVChart} from "ngx-lightweight-charts"; import {CustomData, CustomSeriesOptions, ICustomSeriesPaneView, ISeriesApi, Time} from "lightweight-charts"; @Directive({ selector: '[customSeriesExample]', standalone: true }) export class CustomSeriesExampleDirective<HorzScaleItem = Time> { readonly #collector = inject(TVChartCollectorDirective); data = input<CustomData<HorzScaleItem>[]>(); customSeriesView = input.required<ICustomSeriesPaneView<HorzScaleItem>>({alias: 'customSeriesExample'}); seriesOptions = input<CustomSeriesOptions>({} as CustomSeriesOptions); #series?: ISeriesApi<'Custom', HorzScaleItem>; constructor() { effect(() => { this.#collector.charts()?.forEach((chart: TVChart<'Candlestick', HorzScaleItem>) => { const data = this.data(), customSeriesView= this.customSeriesView(); if(!data || !customSeriesView) { return; } ({ series: this.#series } = chart.addAdditionalSeries('Custom', this.seriesOptions(), customSeriesView)); this.#series?.setData(data); }); }); } } ``` ![Additional custom series!](/assets/additional-custom-series.png "Displaying an additional series") # 6. ### Implemented behaviour --- ### `TVChartGroupDirective` Visually groups multiple charts ```html <div tvChartCollector tvChartGroup> <tv-area-chart [data]="pointData"></tv-area-chart> <tv-histogram-chart [data]="pointData"></tv-histogram-chart> <tv-line-chart [data]="pointData"></tv-line-chart> </div> ``` ![Chart group!](/assets/chart-group.png "Chart group") --- ### `TVChartSyncDirective` Syncs the visible logical range (scale and position) and cross-hair of multiple charts ```html <div tvChartCollector tvChartSync> <tv-candlestick-chart [data]="klineData"></tv-candlestick-chart> <tv-histogram-chart [data]="pointData"></tv-histogram-chart> </div> ``` ![Chart sync!](/assets/chart-sync.png "Chart sync") --- ### `TVChartCrosshairDataDirective` Outputs data relating to the current cross-hair position Single chart: ```html <tv-candlestick-chart [data]="klineData" tvChartCollector (tvChartCrosshairData)="onCrosshairData($event)"></tv-candlestick-chart> ``` ```ts import {Component} from "@angular/core"; import {TVChartCrosshairDataDirective} from "ngx-lightweight-charts"; @Component({ selector: 'app-root', standalone: true, imports: [ TVChartCrosshairDataDirective ], templateUrl: './app.component.html' }) export class AppComponent { onCrosshairData(data: {[key: string | number]: Record<string, any>}): void { /* The format of the data is as follows { [chart id || index]: { // cross-hair point data } } */ // do something with data here... } } ``` Multiple charts: ```html <div tvChartCollector tvChartSync (tvChartCrosshairData)="onCrosshairData($event)"> <tv-candlestick-chart id="ohlc" [data]="klines" [options]="{rightPriceScale: {minimumWidth: 80}}"></tv-candlestick-chart> <div tvChart="Histogram" [data]="rsiValues" [options]="{rightPriceScale: {minimumWidth: 80}}"></div> </div> ``` ```ts import {Component} from "@angular/core"; import {OhlcData, HistogramData, Time} from "lightweight-charts"; import {TVChartCrosshairDataDirective} from "ngx-lightweight-charts"; @Component({ selector: 'app-root', standalone: true, imports: [ TVChartCrosshairDataDirective ], templateUrl: './app.component.html' }) export class AppComponent { klines: OhlcData<Time>[] = [/* loaded kline data */]; rsiData: HistogramData<Time>[] = [/* loaded rsi data */]; onCrosshairData(data: {[key: string | number]: Record<string, any>}): void { /* The format of the data is as follows { ohlc: { time: ..., open: ..., high: ..., low: ..., close: ... }, 2: { time: ..., value: ... } } */ // do something with data here... } } ``` ![Cross-hair data!](/assets/crosshair-data.png "Cross-hair data") # 7. ### Adding behaviour To add your own behaviour it's as simple as doing the following: ```html <div tvChart="Line" [data]="chartData" tvChartCollector yourDirective></div> ``` ```ts import {Directive, effect, inject} from "@angular/core"; import {TVChartCollectorDirective, TVChart} from "ngx-lightweight-charts"; @Directive({ selector: '[yourDirective]', standalone: true }) export class YourDirective { readonly #collector = inject(TVChartCollectorDirective); constructor() { effect(() => { this.#collector.charts()?.forEach((chart: TVChart<any>) => { //... perform some action through the TVChart API }); }); } } ``` [1]: https://tradingview.github.io/lightweight-charts/docs/api/interfaces/TimeChartOptions [2]: https://tradingview.github.io/lightweight-charts/docs/api/interfaces/SeriesPartialOptionsMap [3]: https://tradingview.github.io/lightweight-charts/docs/api/interfaces/SeriesDataItemTypeMap [4]: https://tradingview.github.io/lightweight-charts/docs/api/interfaces/SeriesMarker [5]: https://tradingview.github.io/lightweight-charts/docs/api/interfaces/MouseEventParams [6]: https://tradingview.github.io/lightweight-charts/docs/api/interfaces/Range [7]: https://tradingview.github.io/lightweight-charts/docs/api#logicalrange [8]: https://tradingview.github.io/lightweight-charts/docs/api#datachangedscope [9]: https://tradingview.github.io/lightweight-charts/docs/api#seriestype [10]: https://tradingview.github.io/lightweight-charts/docs/api/interfaces/IChartApi [11]: https://tradingview.github.io/lightweight-charts/docs/api [12]: https://tradingview.github.io/lightweight-charts/docs/api/interfaces/ISeriesApi [13]: https://tradingview.github.io/lightweight-charts/docs/api/interfaces/ITimeScaleApi [14]: https://tradingview.github.io/lightweight-charts/docs/api/interfaces/IPriceScaleApi [15]: https://tradingview.github.io/lightweight-charts/docs/api#time [16]: https://www.tradingview.com/lightweight-charts/ [17]: https://github.com/tradingview/lightweight-charts/tree/master/plugin-examples/src/plugins/hlc-area-series