An easily extendable Angular wrapper for Trading View lightweight-charts
A wrapper that exposes core chart functionality within an Angular app, and allows new functionality to be easily added.
A (re)implementation of the entire lightweight-charts
api.
- Getting started
- Displaying a chart
- Common chart inputs and outputs
- TVChart - accessing the underlying IChartAPI and ISeriesApi instance
- Displaying custom data
- Implemented behaviour
- Adding behaviour
Version | Angular version |
---|---|
0.3.x |
18 |
0.2.x |
17 |
Installation
npm i ngx-lightweight-charts
Add providers
import { ApplicationConfig } from '@angular/core';
import { getTVChartDefaultProviders } from "ngx-lightweight-charts";
export const appConfig: ApplicationConfig = {
providers: [
// ...
getTVChartDefaultProviders()
]
};
There are two ways:
Either use the tvChart
directive, specifying the type of chart to be displayed.
<div tvChart="Line" [data]="lineData"></div>
Or use one of the following convenience components that wrap tvChart
to create a specific chart type.
<tv-area-chart [data]="pointData"></tv-area-chart>
<tv-bar-chart [data]="barData"></tv-bar-chart>
<tv-baseline-chart [data]="pointData"></tv-baseline-chart>
<tv-candlestick-chart [data]="klineData"></tv-candlestick-chart>
<tv-histogram-chart [data]="pointData"></tv-histogram-chart>
<tv-line-chart [data]="pointData"></tv-line-chart>
All charts expose the following signal based inputs and outputs:
(Generic type T
extends SeriesType and HorzItemScale
defaults to Time)
Input | type |
---|---|
id | string |
options | DeepPartial<ChartOptions> |
seriesOptions | SeriesPartialOptionsMap[T] |
data | SeriesDataItemTypeMap<HorzScaleItem>[T][] |
markers | SeriesMarker<HorzScaleItem>[] |
Output | type |
---|---|
initialised | TVChart<T, HorzScaleItem> |
chartClick | MouseEventParams<HorzScaleItem> |
chartDBLClick | MouseEventParams<HorzScaleItem> |
crosshairMoved | MouseEventParams<HorzScaleItem> |
visibleTimeRangeChanged | Range<HorzScaleItem> |
visibleLogicalRangeChanged | LogicalRange | null |
sizeChanged | number |
dataChanged | DataChangedScope |
TVChart - accessing the underlying IChartAPI and ISeriesApi instance
TVChart
is the core class that creates, manages and exposes a trading view chart and its associated series.
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, timeScale, priceScale and series 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
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:
<div tvChart="Line" [data]="chartData" tvChartCollector myDirective></div>
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:
<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>
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:
<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>
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"
});
});
}
}
The following example uses the Custom chart HLC area implementation - source code can be found here
Given the following app component:
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
<tv-custom-series-chart [data]="customData" [customSeriesView]="customSeriesView"></tv-custom-series-chart>
2). By adding an additional (custom) series to an existing chart
<div tvChart="Candlestick" [data]="customData" tvChartCollector [customSeriesExample]="customSeriesView"></div>
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);
});
});
}
}
Visually groups multiple charts
<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>
Syncs the visible logical range (scale and position) and cross-hair of multiple charts
<div tvChartCollector tvChartSync>
<tv-candlestick-chart [data]="klineData"></tv-candlestick-chart>
<tv-histogram-chart [data]="pointData"></tv-histogram-chart>
</div>
Outputs data relating to the current cross-hair position
Single chart:
<tv-candlestick-chart [data]="klineData" tvChartCollector (tvChartCrosshairData)="onCrosshairData($event)"></tv-candlestick-chart>
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:
<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>
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...
}
}
To add your own behaviour it's as simple as doing the following:
<div tvChart="Line" [data]="chartData" tvChartCollector yourDirective></div>
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
});
});
}
}