Working with Data in Angular Stock Chart
Stock Chart can visualize data bound from local or remote data.
Local Data
You can bind a simple JSON data to the chart using dataSource property in series.
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { ChartModule, ChartAllModule, StockChartAllModule } from '@syncfusion/ej2-angular-charts'
import { DateTimeService, LineSeriesService, DateTimeCategoryService, StripLineService} from '@syncfusion/ej2-angular-charts'
import { Component, OnInit } from '@angular/core';
import { chartData } from './datasource';
@Component({
imports: [
ChartModule, StockChartAllModule, ChartAllModule
],
providers: [ DateTimeService, LineSeriesService, DateTimeCategoryService, StripLineService],
standalone: true,
selector: 'app-container',
template: `<ejs-stockchart id="chart-container" [primaryXAxis]='primaryXAxis'[primaryYAxis]='primaryYAxis' [title]='title' [crosshair]='crosshair'>
<e-stockchart-series-collection>
<e-stockchart-series [dataSource]='stockchartData' type='Candle' xName='date' yName='open' name='India' width=2 ></e-stockchart-series>
</e-stockchart-series-collection>
</ejs-stockchart>`
})
export class AppComponent implements OnInit {
public primaryXAxis?: Object;
public primaryYAxis?: Object;
public stockchartData?: Object[];
public title?: string;
public crosshair?: Object;
ngOnInit(): void {
this.stockchartData = chartData;
this.title = 'Efficiency of oil-fired power production';
this.primaryXAxis = {
valueType: 'DateTime',
crosshairTooltip: {enable:true}
};
this.primaryYAxis = {
majorTickLines: { color: 'transparent', width: 0 },
crosshairTooltip: {enable:true}
};
this.crosshair= {
enable: true
};
}
}import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));Remote Data
You can also bind remote data to the chart using DataManager. The DataManager requires minimal information like web service URL, adaptor and crossDomain to interact with service endpoint properly. Assign the instance of DataManager to the dataSource property in series and map the fields of data to xName and yName properties. You can also use the query property of the series to filter the data.
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { ChartModule, ChartAllModule, StockChartAllModule } from '@syncfusion/ej2-angular-charts'
import { DateTimeService, LineSeriesService, DateTimeCategoryService, StripLineService} from '@syncfusion/ej2-angular-charts'
import { Component, OnInit } from '@angular/core';
import { DataManager, Query } from '@syncfusion/ej2-data';
@Component({
imports: [
ChartModule, StockChartAllModule, ChartAllModule
],
providers: [ DateTimeService, LineSeriesService, DateTimeCategoryService, StripLineService],
standalone: true,
selector: 'app-container',
template: `<ejs-stockchart id="stockChartSpline" [enablePeriodSelector]="enable" [chartArea]="chartArea"[primaryXAxis]="primaryXAxis" [primaryYAxis]="primaryYAxis" [seriesType]="seriesType"
[indicatorType]="indicatorType">
<e-stockchart-series-collection>
<e-stockchart-series [dataSource]="dataManager" [query]="query" type="Spline" xName="ShippedDate"
yName="Freight" >
</e-stockchart-series>
</e-stockchart-series-collection>
</ejs-stockchart>`
})
export class AppComponent implements OnInit {
chartArea: any;
ngOnInit(): void {
throw new Error('Method not implemented.');
}
public dataManager: DataManager = new DataManager({
url: "https://ej2services.syncfusion.com/production/web-services/api/Orders"
});
public query: Query = new Query()
.take(5)
.where("Estimate", "lessThan", 50, false);
public primaryXAxis: Object = {
valueType: "DateTime",
crosshairTooltip: { enable: true },
majorGridLines: { width: 0 }
};
public primaryYAxis: Object = {
lineStyle: { width: 0 },
majorTickLines: { width: 0 }
};
public seriesType: string[] = [];
public indicatorType: string[] = [];
public periods: object[] = [];
public enable: boolean = false;
}import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));Handling No Data
When no data is available to render in the stock chart, the noDataTemplate property can be used to display a custom layout within the chart area. This layout may include a message indicating the absence of data, a relevant image, or a button to initiate data loading. Styled text, images, or interactive elements can be incorporated to maintain design consistency and improve user guidance. Once data becomes available, the chart automatically updates to display the appropriate visualization.
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { ChartModule, ChartAllModule, StockChartAllModule } from '@syncfusion/ej2-angular-charts'
import { DateTimeService, LineSeriesService, DateTimeCategoryService, StripLineService} from '@syncfusion/ej2-angular-charts'
import { Component, OnInit } from '@angular/core';
import { chartData } from './datasource';
@Component({
imports: [
ChartModule, StockChartAllModule, ChartAllModule
],
providers: [ DateTimeService, LineSeriesService, DateTimeCategoryService, StripLineService],
standalone: true,
selector: 'app-container',
template: `<ejs-stockchart id="chart-container" [noDataTemplate]='noDataTemplate' #chart [primaryXAxis]='primaryXAxis'[primaryYAxis]='primaryYAxis' [title]='title' [crosshair]='crosshair'>
<e-stockchart-series-collection>
<e-stockchart-series [dataSource]='stockchartData' type='Candle' xName='date' yName='open' name='India' width=2 ></e-stockchart-series>
</e-stockchart-series-collection>
<ng-template #noDataTemplate>
<div id="noDataTemplateContainer">
<div class="template-align">
<img src="./no-data.png" alt="No Data"/>
</div>
<div class="template-align">
<p style="font-size: 15px; margin: 10px 0 0;"><strong>No data available to display.</strong></p>
</div>
<div class="template-align" style="margin-top: 15px;">
<button ejs-button class="load-data-btn" (click)="loadData()" iconCss="e-icons e-refresh">Load Data</button>
</div>
</div>
</ng-template>
</ejs-stockchart>`
})
export class AppComponent implements OnInit {
public primaryXAxis?: Object;
public primaryYAxis?: Object;
public stockchartData?: Object[];
public title?: string;
public crosshair?: Object;
ngOnInit(): void {
this.stockchartData = [];
this.title = 'Efficiency of oil-fired power production';
this.primaryXAxis = {
valueType: 'DateTime',
crosshairTooltip: {enable:true}
};
this.primaryYAxis = {
majorTickLines: { color: 'transparent', width: 0 },
crosshairTooltip: {enable:true}
};
this.crosshair= {
enable: true
};
}
public loadData(): void {
this.stockchartData = chartData;
}
}import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));Live Stock Chart
The Stock Chart can be updated in real time by pushing new and modified OHLC (open, high, low, close, volume) values into an existing candle series without re-rendering the entire chart. This is achieved with the help of the addPoint and setData methods exposed on a StockSeries instance.
-
setData(point, animationDuration)– replaces the current forming candle with the supplied point. Use this when you want to update the price of the candle that is currently being built. -
addPoint(point, animationDuration)– appends a brand-new candle to the end of the series. Use this when a new time bucket starts (for example, every new minute on a one-minute candle).
A typical real-time workflow uses both methods together:
- Start with a set of historical candles loaded through
dataSource. - On every tick, call
setDatato mutate the values of the in-progress candle. - When the candle interval elapses, call
addPointto begin a new candle whose open price matches the previous close.
In the following example, simulated one-minute OHLC data is generated locally and the chart is updated every 100 ms (using setInterval) to demonstrate live behavior. The forming candle is updated with setData, and a new candle is appended with addPoint every ten ticks.
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ChartAllModule, StockChartAllModule, StockChartComponent, StockChartModule } from '@syncfusion/ej2-angular-charts';
interface StockData {
x: Date;
open: number;
high: number;
low: number;
close: number;
volume: number;
}
/**
* Simulated one-minute candle in milliseconds.
*/
const ONE_MINUTE_MS: number = 60 * 1000;
/**
* Tick frequency for updating the chart.
* The short interval is used only for demonstrating live updates.
*/
const UPDATE_INTERVAL_MS: number = 100;
/**
* A new one-minute candle is added after this many updates.
*/
const UPDATES_PER_CANDLE: number = 10;
/**
* Animation duration while updating the current candle.
*/
const UPDATE_ANIMATION_DURATION: number = 0;
/**
* Animation duration while adding a new candle.
*/
const ADD_ANIMATION_DURATION: number = 300;
/**
* Number of initial candles.
*/
const INITIAL_CANDLE_COUNT: number = 120;
/**
* Initial stock price.
*/
const INITIAL_PRICE: number = 375;
@Component({
imports: [StockChartModule, StockChartAllModule, ChartAllModule],
standalone: true,
selector: 'app-container',
template: `
<div class="control-section" style="padding: 5%;">
<ejs-stockchart id="chart-container"
#stockRef
[width]="width"
[height]="height"
[title]="title"
[theme]="theme"
[chartArea]="chartArea"
[primaryXAxis]="primaryXAxis"
[primaryYAxis]="primaryYAxis"
[series]="series"
[seriesType]="seriesType"
[indicatorType]="indicatorType"
[trendlineType]="trendlineType"
[periods]="periods"
[enableCustomRange]="enableCustomRange"
(loaded)="onChartLoaded()">
</ejs-stockchart>
</div>
`
})
export class AppComponent implements OnInit, OnDestroy {
@ViewChild('stockRef')
public stockObj!: StockChartComponent;
public width: string = '1100px';
public height: string = '455px';
public title: string = 'Live Stock Data';
public theme: string = 'Material';
public chartArea: Object = {
border: {
width: 0
}
};
public primaryXAxis: Object = {
valueType: 'DateTime',
labelFormat: 'HH:mm'
};
public primaryYAxis: Object = {
labelPosition: 'Outside'
};
public series: Object[] = [];
public seriesType: string[] = [];
public indicatorType: string[] = [];
public trendlineType: string[] = [];
public periods: Object[] = [
{
text: '1H',
interval: 1,
intervalType: 'Hours',
selected: true
},
{
text: 'All'
}
];
public enableCustomRange: boolean = false;
private stockData: StockData[] = [];
private updateTimer: number | null = null;
private updateIndex: number = 0;
public ngOnInit(): void {
this.createInitialLocalData();
this.series = [
{
dataSource: this.stockData,
type: 'Candle',
xName: 'x',
open: 'open',
high: 'high',
low: 'low',
close: 'close',
volume: 'volume',
lastValueLabel: {
enable: true,
background: '#e74c3d',
dashArray: '3,2',
lineWidth: 1,
font: {
color: '#ffffff',
size: '11px'
}
}
}
];
}
/**
* Starts live updates after the Stock Chart has been loaded.
*/
public onChartLoaded(): void {
this.startDynamicUpdates();
}
/**
* Stops the timer when the Angular component is destroyed.
*/
public ngOnDestroy(): void {
this.stopDynamicUpdates();
}
/**
* Returns a random value between the specified minimum and maximum.
*/
private randomBetween(minimum: number, maximum: number): number {
return minimum + Math.random() * (maximum - minimum);
}
/**
* Rounds a value to two decimal places.
*/
private correctFloat(value: number): number {
return Number(value.toFixed(2));
}
/**
* Returns the timestamp for the beginning of the current minute.
*/
private getCurrentMinute(): number {
return Math.floor(Date.now() / ONE_MINUTE_MS) * ONE_MINUTE_MS;
}
/**
* Creates a historical OHLCV candle.
*/
private createHistoricalCandle(
timestamp: number,
openingPrice: number
): StockData {
const movement: number = this.randomBetween(-1.5, 1.5);
const close: number = this.correctFloat(
Math.max(1, openingPrice + movement)
);
const high: number = this.correctFloat(
Math.max(openingPrice, close) + this.randomBetween(0.05, 0.7)
);
const low: number = this.correctFloat(
Math.max(
1,
Math.min(openingPrice, close) - this.randomBetween(0.05, 0.7)
)
);
return {
x: new Date(timestamp),
open: this.correctFloat(openingPrice),
high: high,
low: low,
close: close,
volume: Math.round(this.randomBetween(1000, 5000))
};
}
/**
* Creates the initial historical stock data.
*/
private createInitialLocalData(): void {
const currentMinute: number = this.getCurrentMinute();
const startingTime: number =
currentMinute - (INITIAL_CANDLE_COUNT - 1) * ONE_MINUTE_MS;
let price: number = INITIAL_PRICE;
this.stockData = [];
for (
let index: number = 0;
index < INITIAL_CANDLE_COUNT;
index++
) {
const timestamp: number =
startingTime + index * ONE_MINUTE_MS;
const candle: StockData =
this.createHistoricalCandle(timestamp, price);
this.stockData.push(candle);
price = candle.close;
}
}
/**
* Returns the Candle series instance that supports
* setData and addPoint.
*/
private getCandleSeries(): any {
const chart: any = this.stockObj as any;
if (
chart &&
chart.series &&
chart.series.length > 0 &&
typeof chart.series[0].setData === 'function' &&
typeof chart.series[0].addPoint === 'function'
) {
return chart.series[0];
}
return null;
}
/**
* Creates an updated version of the currently active candle.
*/
private createUpdatedCandle(
currentCandle: StockData
): StockData {
const movement: number = this.correctFloat(
this.randomBetween(-1.5, 1.5)
);
const newClose: number = this.correctFloat(
Math.max(1, currentCandle.close + movement)
);
return {
x: currentCandle.x,
open: currentCandle.open,
high: this.correctFloat(
Math.max(currentCandle.high, newClose)
),
low: this.correctFloat(
Math.min(currentCandle.low, newClose)
),
close: newClose,
volume:
currentCandle.volume +
Math.round(this.randomBetween(10, 100))
};
}
/**
* Creates the next candle using the previous candle's close value.
*/
private createNewCandle(
previousCandle: StockData
): StockData {
const openingPrice: number = previousCandle.close;
return {
x: new Date(
previousCandle.x.getTime() + ONE_MINUTE_MS
),
open: openingPrice,
high: openingPrice,
low: openingPrice,
close: openingPrice,
volume: Math.round(this.randomBetween(1000, 5000))
};
}
/**
* Updates the currently active candle using setData.
*/
private updateCurrentPoint(candle: StockData): void {
const series: any = this.getCandleSeries();
if (!series) {
return;
}
series.setData(candle, UPDATE_ANIMATION_DURATION);
}
/**
* Adds a new candle using addPoint.
*/
private addNewPoint(candle: StockData): void {
const series: any = this.getCandleSeries();
if (!series) {
return;
}
series.addPoint(candle, ADD_ANIMATION_DURATION);
}
/**
* Updates the active candle or adds a new candle.
*/
private processDynamicUpdate = (): void => {
if (!this.stockData.length) {
return;
}
const lastIndex: number = this.stockData.length - 1;
const currentCandle: StockData = this.stockData[lastIndex];
const shouldAddNewCandle: boolean =
this.updateIndex > 0 &&
this.updateIndex % UPDATES_PER_CANDLE === 0;
if (shouldAddNewCandle) {
const newCandle: StockData =
this.createNewCandle(currentCandle);
this.stockData.push(newCandle);
this.addNewPoint(newCandle);
} else {
const updatedCandle: StockData =
this.createUpdatedCandle(currentCandle);
this.stockData[lastIndex] = updatedCandle;
this.updateCurrentPoint(updatedCandle);
}
this.updateIndex++;
};
/**
* Starts the live stock update timer.
*/
private startDynamicUpdates(): void {
if (this.updateTimer !== null) {
return;
}
this.updateTimer = window.setInterval(
this.processDynamicUpdate,
UPDATE_INTERVAL_MS
);
}
/**
* Stops the live stock update timer.
*/
private stopDynamicUpdates(): void {
if (this.updateTimer === null) {
return;
}
window.clearInterval(this.updateTimer);
this.updateTimer = null;
}
}import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
bootstrapApplication(AppComponent).catch((err) => console.error(err));