Blogs

Create Timeline Charts with Apex Charts in Angular

Category
Software development
Create Timeline Charts with Apex Charts 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/donut 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 travelling 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 which colour changes depending on the location of the entity.

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 back in August of 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 donut 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 Apex Charts library. For this purpose we will create a small angular project that will consume data of our favourite Pac-man ghosts moving through zones and display that as the timeline chart, as shown in the image below:

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

Format of the returned data we will consume and 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
        ]
      }
    ]
  },
]

This sample response only contains info about 2 ghosts (their names and movement) and 2 zones (the names of the zone). Full file, used for the chart above, can be found here: https://github.com/ag04/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 angular app, go ahead clone the project, open it in the terminal and run the following two commands:

npm install
npm start

Once this is done, open your favourite browser, point it to http://localhost:4200, and you should see the same output as on 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 

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

(/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 thia 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;
    }
}

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

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

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

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

ng generate component timeline-chart

Modify generated component’s typescript file by adding the following imports:

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

(/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 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"
      }
    };
  }

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

(/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>

(/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>

(/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 has entered and left the zone. To achieve this we need to 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;
};

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

(/src/app/timeline-chart/timeline-chart.component.ts)
Additionally, we must also bind 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>

(/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:

Conclusion

Congratulations by 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 and straightforward.

If you want to experiment more, let’s say by changing labels, having animations and custom interactions, have a 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

Next

Blog

Don’t put your devs into a Scrum Master role!

Dont-put-your-devs-into-a-Scrum-Master-role

Company

Our people really love it here

Evolution of expertise

The agency was founded in 2014 by seasoned industry veterans with experience from large enterprises. A group of Java engineers then evolved into a full-service company that builds complex web and mobile applications. 

Flexibility is our strong suit — both large enterprises and startups use our services. We are now widely recognized as the leading regional experts in the Java platform and Agile approach.

We make big things happen and we are proud of that.

Personal development

If you join our ranks you’ll be able hone your coding skills on relevant projects for international clients.

Our diverse client portfolio enables our engineers to work on complex long term projects like core technologies for Delivery Hero, Blockchain and NFTs for Fantasy Football or cockpit interface for giants like Strabag. 

Constant education and knowledge sharing are part of our company culture. Highly experienced mentors will help you out and provide real-time feedback and support.

Contact

We’d love to hear from you