Blog

Create Timeline Charts with ApexCharts in Angular

Category
Software development
Create Timeline Charts with ApexCharts in Angular

ApexCharts in Angular? Until now, I have been using the Charts.js open-source library for creating charts for projects I have been working on. Creating stacked bar charts and pie/doughnut charts was a walk in the park until I was supposed to implement a timeline chart for multiple entities.

The case is like this: multiple entities (smartphones and beacons) are changing their physical position in the real world. By changing their position they are traveling between various zones. A timeline chart needs to show their movement throughout the various locations.

The timeline chart should show the various entities on the Y axis (vertical axis), the flow of time on the X axis (horizontal axis), and a line whose color changes depending on the location of the entity.

apexcharts angular

Now here is the catch; Chart.js does not support the creation of a timeline chart. It supports a horizontal bar chart which is not even close to what we need. The closest to having a functional timeline chart with Chart.js is an open-source half-unfinished library on github which has not been updated for almost 2 years.


Since this library was last updated in August 2020, my team and I decided to switch to a completely different library for creating charts. The decision fell on the ApexCharts.js open-source library. It has all we need; a stacked bar chart, a pie chart (later, we used a doughnut chart), and most importantly a timeline chart for multiple grouped rows.

What will we be building in this blog?

In this blog post, I will demonstrate how to create timeline charts using the Apex Charts library. For this purpose, we will create a small angular project that will consume data of our favorite Pac-man ghosts moving through zones and display that as the timeline chart, as shown in the image below:

apexcharts angular

For the reasons of simplicity, we will not consume any backend services. Still, we will instead fetch data for our chart from the static file (located in src/assets/data.json), which simulates the response of the actual endpoint that returns telemetry data of the ghost’s movement through the zones.

Integrating ApexCharts into an Angular application is a straightforward process.

The format of the returned data we will consume, and the display is as follows:

[
  {
    "name": "Zone_1",
    "data": [
      {
        "x": "Blinky",
        "y": [
          1641031200000, // start time of the period (in ms)
          1641034799000  // end of the period (in ms)
        ]
      },
      {
        "x": "Pinky",
        "y": [
          1641024000000,
          1641032999000
        ]
      }
    ]
  },
 {
    "name": "Zone_2",
    "data": [
      {
        "x": "Blinky",
        "y": [
          1641034800000,
          1641038399000
        ]
      }
    ]
  },
]
Code language: JSON / JSON with Comments (json)

This sample response only contains info about 2 ghosts (their names and movement) and 2 zones (the names of the zone). The full file, used for the chart above, can be found here: https://github.com/wearenotch/apexcharts-timeline-demo/blob/main/src/assets/data.json


The entire project is available as a public repository on GitHub. If you do now want to follow this blog hands-on and step-by-step build an angular app, go ahead and clone the project, open it in the terminal, and run the following two commands:

npm install
npm start

Once this is done, open your favorite browser, point it to http://localhost:4200, and you should see the same output as in the image above.


As a first step, we will create a new Angular application using Angular CLI. Open the terminal and position yourself in the folder of your choice and execute the following:

ng new my-apex-timeline-chart Code language: JavaScript (javascript)

When prompted simply accept all defaults. Once the process has finished, import the generated application in your IDE of choice and we can start building. 

Now it is time to install ng-apex chart library, do this by executing the following command:

npm install ng-apexcharts --save

Followed up by modifying the app.module.ts as shown in the next code snippet:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
 
import { AppComponent } from './app.component';
import { NgApexchartsModule } from 'ng-apexcharts';
 
@NgModule({
 declarations: [
   AppComponent,
 ],
 imports: [
   BrowserModule,
   HttpClientModule,
   NgApexchartsModule
 ],
 providers: [],
 bootstrap: [AppComponent]
})
export class AppModule { }Code language: JavaScript (javascript)

(/src/app/app.module.ts)

To ensure that we have some data to consume for our chart, copy the data.json file from this url to the src/assets/ folder of your new project.

We need some data model classes that will match the model of the data in this JSON file, which we will later provide to the chart for display. For this purpose we will create the following two classes:

  • StackedTimelineChartData
  • TimelineChartData

The response class StackedTimelineChartData looks like this:

import { TimelineChartData } from "./timeline-chart-data.model";
export class StackedTimelineChartData {
    // name of the timeline “period”, or in our case
    // the name of the zone in which the entity was located
    public name: string;
    // a separate class for the data connected with that period
    public data: TimelineChartData[];
    constructor(name: string, data: TimelineChartData[]) {
        this.name = name;
        this.data = data;
    }
}Code language: JavaScript (javascript)

(src/app/model/stacked-timeline-chart-data.model.ts)

While the TimelineChartData class looks like this:

export class TimelineChartData {
    // name of the entity    
    public x: any;
    /* the first and the last time this entity was seen
       in the zone, the value is shown in
       milliseconds (Unix time) */ 
    public y: any[];
    constructor(x: any, y: any[]) {
        this.x = x;
        this.y = y;
    }
}Code language: JavaScript (javascript)

(src/app/model/timeline-chart-data.model.ts)

Go ahead and create both of these files, with the content as shown in the code snippets above, inside the src/app/model folder.

To be able to consume src/app/assets/data.json file we need a “mock” DataService class that will fetch this file. The mock service looks like this:

import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { StackedTimelineChartData } from "../models/stacked-timeline-chart-data.model";
@Injectable({ providedIn: 'root' })
export class DataService {
  resourceUrl = "/assets/data.json";
  constructor(
	private httpClient: HttpClient
  ) { }
  getTimelineData(): Observable<StackedTimelineChartData[]> {
	return this.httpClient.get<StackedTimelineChartData[]>(this.resourceUrl);
  }
}Code language: JavaScript (javascript)

(src/app/model/service/data.service.ts)

With these pieces in place, we can now create a component that will hold a timeline chart. Generate new component by executing:

ng generate component timeline-chart

Modify generated component’s typescript file by adding the following imports to continue the installation of ApexCharts in Angular:

import { Component, OnInit, ViewChild } from '@angular/core';
import { StackedTimelineChartData } from '../model/stacked-timeline-chart-data.model';
import { DataService } from '../service/data.service';
import {
  ChartComponent,
  ApexAxisChartSeries,
  ApexChart,
  ApexPlotOptions,
  ApexFill,
  ApexXAxis,
  ApexLegend,
} from "ng-apexcharts";
import { TimelineChartData } from '../models/timeline-chart-data.model';
export type ChartOptions = {
  series: ApexAxisChartSeries;
  chart: ApexChart;
  plotOptions: ApexPlotOptions;
  fill: ApexFill;
  xaxis: ApexXAxis;
  legend: ApexLegend;
};Code language: JavaScript (javascript)

(/src/app/timeline-chart/timeline-chart.component.ts)

Here is the official documentation on the chart’s options. The initialization of the Chart and assignment of the chart’s options looks like this:

@Component({
  selector: 'app-timeline-chart',
  templateUrl: './timeline-chart.component.html',
  styleUrls: ['./timeline-chart.component.css']
})
export class TimelineChartComponent implements OnInit {
  data: StackedTimelineChartData[] = [];
  @ViewChild("chart", { static: false })
  chart!: ChartComponent;
  public chartOptions: Partial<ChartOptions>;
  constructor(
    private dataService: DataService
  ) {
    this.chartOptions = {
      // when initialised, that chart will not have any data
      // data will be assigned after getting it from the backend
      series: [],
      chart: {
        height: 350,
        type: "rangeBar"
      },
      plotOptions: {
        bar: {
          horizontal: true,
            barHeight: "50%",
            rangeBarGroupRows: true
        }
      },
      fill: {
        type: "solid"
      },
      xaxis: {
        type: "datetime"
      },
      legend: {
        position: "right"
      }
    };
  }Code language: PHP (php)

(/src/app/timeline-chart/timeline-chart.component.ts)

To retrieve data from the service, a subscription is called in the ngOnInit() method.

ngOnInit(): void {
  this.dataService.getTimelineData().subscribe(
    res => {
      this.data = res;
      this.chartOptions.series = this.data;
    }
  );
}Code language: JavaScript (javascript)

(/src/app/timeline-chart/timeline-chart.component.ts)

The next step is to modify the HTML code of the component. Replace the content of the timeline-chart.component.html file so that it should look like this:

<h1 style="text-align: center;">Ghosts roaming Timeline Chart</h1>
<div id="chart" style="width: 80%; margin: 0 auto;">
  <apx-chart
    [series]="chartOptions.series!"
    [chart]="chartOptions.chart!"
    [plotOptions]="chartOptions.plotOptions!"
    [fill]="chartOptions.fill!"
    [xaxis]="chartOptions.xaxis!"
    [legend]="chartOptions.legend!"
  ></apx-chart>
</div>Code language: HTML, XML (xml)

(/src/app/timeline-chart/timeline-chart.component.html)

Finally what remains to be done is to modify the content of the /src/app/app.component.html file. Replace the entire content of the file with the following:

<app-timeline-chart></app-timeline-chart>Code language: HTML, XML (xml)

(/src/app/app.component.html)

And that is it. We can now run the application by issuing the command:

npm start

And if all went well, when you open the URL: http://localhost:4200,  you should see the image as shown at the beginning of this post.

Customising tooltips

By default, the tooltip shown when hovering over the chart displays the start and end dates without the time (hours, minutes and seconds). It looks like this:

These default settings aren’t exactly what we want. Since we would like to know the exact time when one of our ghosts entered and left the zone. To achieve this, we must override the chart’s default tooltip structure.

In the TimelineChartComponent import an interface ApexTooltip from “ng-apexcharts”. Then define the import in ChartOptions. Now the import section should look like this:

import {
  ChartComponent,
  ApexAxisChartSeries,
  ApexChart,
  ApexPlotOptions,
  ApexFill,
  ApexXAxis,
  ApexLegend,
  ApexTooltip,
} from "ng-apexcharts";
export type ChartOptions = {
  series: ApexAxisChartSeries;
  chart: ApexChart;
  plotOptions: ApexPlotOptions;
  fill: ApexFill;
  xaxis: ApexXAxis;
  legend: ApexLegend;
  tooltip: ApexTooltip;
};Code language: JavaScript (javascript)

Modify the constructor by adding the following block of code at the end of this.chartOptions

this.chartOptions = {
  // the previous options are omitted in this example
  tooltip: {
    custom: function ({ series, seriesIndex, dataPointIndex, w }) {
      var data = w.globals.initialSeries[seriesIndex].data[dataPointIndex];
      const seriesName = w.globals.initialSeries[seriesIndex].name;
      const enterDate = new Date(data.y[0]).toLocaleString('en-GB', { timeZone: 'UTC' });
      const exitDate = new Date(data.y[1]).toLocaleString('en-GB', { timeZone: 'UTC' });
      return '<div class="apexcharts-tooltip-rangebar"> <div> <span class="series-name" style="color: #FF4560">' +
        seriesName +
        '</span></div><div> <span class="category">Inky: </span> <span class="value start-value">' +
        enterDate +
        '</span> <span class="separator">-</span> <span class="value end-value">' +
        exitDate +
        '</span></div></div>';
  }
}Code language: JavaScript (javascript)

(/src/app/timeline-chart/timeline-chart.component.ts)
Additionally, we must also bind the tooltip options we have modified in the HTML code of the time-chart.component, as shown in the following code snippet:

<apx-chart
  [series]="chartOptions.series!"
  [chart]="chartOptions.chart!"
  [plotOptions]="chartOptions.plotOptions!"
  [fill]="chartOptions.fill!"
  [xaxis]="chartOptions.xaxis!"
  [legend]="chartOptions.legend!"
  [tooltip]="chartOptions.tooltip!"
  ></apx-chart>Code language: HTML, XML (xml)

(/src/app/timeline-chart/timeline-chart.component.html)

Now, rerun the application and check the chart tooltip. It should look as in the image below:

So, congratulations – you successfully installed ApexCharts in Angular!

Conclusion

Congratulations on finishing reading this blog and following all the steps in the tutorial. Now you know how to create timeline charts. As you can see, creating timeline charts with ApexCharts.js in Angular is easy.

With these steps, you can easily integrate ApexCharts into your Angular application and build beautiful and interactive charts and graphs.

If you want to experiment more, let’s say by changing labels, having animations and custom interactions, look at the official documentation. Happy coding!

Resources:

Project Github repository: https://github.com/ag04/apexcharts-timeline-demo

ApexChart project page: https://apexcharts.com

ApexChart documentation: https://apexcharts.com/docs/installation/

AngularCLI (optional): https://angular.io/cli

CONTACT US

Exceptional ideas need experienced partners.