ngx-lightweight-charts
Version:
Angular wrapper for Trading View lightweight-charts
517 lines (393 loc) • 14.8 kB
Markdown
# 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";
({
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";
({
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";
({
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";
({
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";
({
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>
```

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";
({
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);
});
});
}
}
```

# 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>
```

---
### `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>
```

---
### `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";
({
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";
({
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...
}
}
```

# 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";
({
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