Skip to content

jamesbrobb/ngx-lightweight-charts

Repository files navigation

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.

How to use.


  1. Getting started
  2. Displaying a chart
  3. Common chart inputs and outputs
  4. TVChart - accessing the underlying IChartAPI and ISeriesApi instance
  5. Displaying custom data
  6. Implemented behaviour
  7. Adding behaviour

1.

Getting started

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()
  ]
};

2.

Displaying a chart

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>

3.

Common chart inputs and outputs

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

4.

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

5.

Displaying custom data

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>

Custom series!

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);
      });
    });
  }
}

Additional custom series!

6.

Implemented behaviour


TVChartGroupDirective

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>

Chart group!


TVChartSyncDirective

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>

Chart sync!


TVChartCrosshairDataDirective

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...
  }
}

Cross-hair data!

7.

Adding behaviour

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
      });
    });
  }
}

About

Angular wrapper for Trading View lightweight-charts

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published