Home | Send Feedback

Integrate ECharts into an Ionic 4 app

Published: February 22, 2017  •  Updated: September 29, 2018  •  ionic4, javascript

In this post I show you how to integrate the ECharts library into an Ionic 4 application. ECharts is an open source, free to use chart library, released under the (BSD license). The library is developed by Baidu, the Chinese search company. ECharts draws diagrams and charts into a canvas element and it added recently a renderer that supports SVG.

The library provides a lot of chart types out of the box. The demo page is a good place to start and see all the diagrams and charts in action: https://ecomfe.github.io/echarts-examples/public/index.html

The documentation page of ECharts describes all the configuration options and methods
Api: https://ecomfe.github.io/echarts-doc/public/en/api.html#echarts
Option: https://ecomfe.github.io/echarts-doc/public/en/option.html


What we want to do in this post is creating an Ionic 4 app and display some simple charts. For the following examples I copied the configuration objects directly from the ECharts demo page. The application is online at this URL: https://demo.rasc.ch/echarts/

Setup

We start with the Ionic command line tool and create a new app based on the tabs template.

ionic start echartsapp tabs --type=angular

Next we install the ECharts library and the ngx-echarts library which provides an Angular directive that allows us to integrate ECharts into an Angular app very easily. See also the project page of ngx-echarts for the latest documentation.

npm install echarts
npm install ngx-echarts

To use the ngx-echarts directive, we have to import the module. Import NgxEchartsModule in the tabs module file.

import {NgxEchartsModule} from 'ngx-echarts';

@NgModule({
  imports: [
    IonicModule,
    CommonModule,
    NgxEchartsModule,
    TabsPageRoutingModule
  ],

tabs.module.ts

Then we create a few pages that will host our examples.

ionic g page chart1 --spec=false
ionic g page chart2 --spec=false
ionic g page chart3 --spec=false
ionic g page chart4 --spec=false
ionic g page chart5 --spec=false
ionic g page chart6 --spec=false

You can delete all the other pages that the start command created (about, contact, home). The generate command also created modules file for each page. You can delete them because we don't use lazy loading inside the tabs module.

Next add the generated pages to the src/app/tabs/tabs.router.module.ts file.

Directive

Before we continue with the example let's take a quick look at the ngx-echarts library and the echarts directive that the library provides. To display an ECharts diagram you add the echarts directory to a div tag in your template.

<div echarts [options]="chartOption"></div>

The input property options configures the chart.

// ...

chartOption: {
  xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  },
  yAxis: {
    type: 'value'
  },
  series: [{
    data: [820, 932, 901, 934, 1290, 1330, 1320],
    type: 'line'
  }]
}

The echarts directive supports the following properties

Property Description
[options] The object that describes the chart. You find a lot of examples on the ECharts example page. Often you can simply copy and paste the option object from there and tweak it according your needs
[merge] Used for updating parts of the options. The object will be merged with echartsInstance.setOption() and notMerge = false
[loading] Shows the ECharts loading animation when the data is not ready. Defaults to false
[loadingOpts] Option to customize the loading animation. Only used when [loading] is true. See the documentation for the supported configuration properties.
[autoResize] Automatically resizes the chart when the size of the parent container changes. By default true
[initOpts] Object used for the echarts.init() call. For example, you can change the renderer from canvas to SVG
[theme] String that specifies the theme to use. In the following second example we will see a different theme
[detectEventChanges] Specifies if a mouse event handler should be registered on the echart instance. By default set to true.

Chart 1

The template for the first example is very simple. We add the echarts directive to a div tag and inject the options object from the TypeScript code into the directive.

<ion-content>
  <div echarts [options]="options" class="demo-chart"></div>
</ion-content>

chart1.html

In the TypeScript file we set up the options object.

import {Component} from '@angular/core';

@Component({
  selector: 'page-chart1',
  templateUrl: 'chart1.html',
  styleUrls: ['chart1.scss']
})
export class Chart1Page {

  options = {
    color: ['#3398DB'],
    grid: {
      left: '3%',
      right: '4%',
      bottom: '3%',
      containLabel: true
    },
    xAxis: [
      {
        type: 'category',
        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
        axisTick: {
          alignWithLabel: true
        }
      }
    ],
    yAxis: [
      {
        type: 'value'
      }
    ],
    series: [
      {
        name: 'Test',
        type: 'bar',
        barWidth: '60%',
        data: [10, 52, 200, 334, 390, 330, 220]
      }
    ]
  };

}

chart1.ts

Finally, we add some styles to the SCSS file. We have to assign a width and a height to the div element that surrounds the canvas.

@media screen and (orientation: portrait) {
  .demo-chart {
    width: 100vw;
    height: 82vh;
  }
}

@media screen and (orientation: landscape) {
  .demo-chart {
    width: 100vw;
    height: 68vh;
  }
}



chart1.scss

Now you can start the app with ionic serve and you should see the following chart on the first tab. Chart1

Chart 2

The second chart is built the same way as the first one. The only difference is the ECharts config object.

<ion-content>
  <div echarts [options]="options" theme="dark" class="demo-chart"></div>
</ion-content>

chart2.html

import {Component} from '@angular/core';

@Component({
  selector: 'page-chart2',
  templateUrl: 'chart2.html',
  styleUrls: ['chart2.scss']
})
export class Chart2Page {

  options = {
    legend: {
      data: ['Profit', 'Expenses', 'Income']
    },
    grid: {
      left: '3%',
      right: '4%',
      bottom: '3%',
      containLabel: true
    },
    xAxis: [
      {
        type: 'value'
      }
    ],
    yAxis: [
      {
        type: 'category',
        axisTick: {show: false},
        data: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
      }
    ],
    series: [
      {
        name: 'Profit',
        type: 'bar',
        label: {
          normal: {
            show: true,
            position: 'inside'
          }
        },
        data: [200, 170, 240, 244, 200, 220, 210]
      },
      {
        name: 'Income',
        type: 'bar',
        stack: 'Total',
        label: {
          normal: {
            show: true
          }
        },
        data: [320, 302, 341, 374, 390, 450, 420]
      },
      {
        name: 'Expenses',
        type: 'bar',
        stack: 'Total',
        label: {
          normal: {
            show: true,
            position: 'left'
          }
        },
        data: [-120, -132, -101, -134, -190, -230, -210]
      }
    ]
  };
}

chart2.ts

For this example we use a different theme. In the template we specify the theme with theme="dark".

The ECharts library provides, in the current version, 6 pre-built themes. You find the names of the themes in the node_modules/echarts/theme directory.

Another thing I want to demonstrate with this example is, that the charts have some interactivity built in. In this chart you can click or tap on the legend items and the chart will hide or show the corresponding data series.

chart2_1 chart2_2

Chart 3

The third chart demonstrates a way to update the data of a chart on the fly. When you click or tap the start button the application starts a function with setInterval that updates the data every two seconds with a random value.

The echarts directive supports the options property that contains the initial configuration and the merge property which allows an application to update parts of the options.

<ion-header>
  <ion-toolbar>
    <ion-title>Chart 3</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <div echarts [options]="options" [merge]="datas" class="demo-chart"></div>
</ion-content>

<ion-footer>
  <ion-button expand="block" [disabled]="running" (click)="start()" color="secondary">Start</ion-button>
  <ion-button expand="block" [disabled]="!running" (click)="stop()" color="danger">Stop</ion-button>
</ion-footer>

chart3.html

It's important that the applications assigns a new object to the datas instance variable each time it wants to set a new value.

this.datas = {
  series: [{
    data: [{value: Number((Math.random() * 100).toFixed(1))}]
  }]
};

It would not work when you only change the value like this
this.datas.series[0].data[0].value = Number((Math.random() * 100).toFixed(1))
The echarts directive only picks up the change when the reference to the datas object changes.

import {Component} from '@angular/core';

@Component({
  selector: 'page-chart3',
  templateUrl: 'chart3.html',
  styleUrls: ['chart3.scss']
})
export class Chart3Page {

  running = false;
  options = {
    series: [{
      type: 'gauge',
      detail: {formatter: '{value}%'},
      data: [{value: 50, name: 'Sensor'}]
    }]
  };
  datas: any = null;
  private interval = null;

  start() {
    this.running = true;
    this.interval = setInterval(() => {
      this.datas = {
        series: [{
          data: [{value: Number((Math.random() * 100).toFixed(1))}]
        }]
      };
    }, 2000);
  }

  stop() {
    this.running = false;
    clearInterval(this.interval);
  }

}

chart3.ts

Chart3

Chart 4

The fourth example displays a simple graph. The template page and TypeScript code are similar to the other examples.

<ion-header>
  <ion-toolbar>
    <ion-title>Chart 4</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <div echarts [options]="options" class="demo-chart"></div>
</ion-content>

chart4.html

import {Component} from '@angular/core';


@Component({
  selector: 'page-chart4',
  templateUrl: 'chart4.html',
  styleUrls: ['chart4.scss']
})
export class Chart4Page {

  options = {
    title: {
      text: 'Graph Example'
    },
    series: [
      {
        type: 'graph',
        layout: 'none',
        symbolSize: 50,
        roam: true,
        label: {
          normal: {
            show: true
          }
        },
        edgeSymbol: ['circle', 'arrow'],
        edgeSymbolSize: [4, 10],
        edgeLabel: {
          normal: {
            textStyle: {
              fontSize: 20
            }
          }
        },
        data: [{
          name: 'Node 1',
          x: 300,
          y: 300
        }, {
          name: 'Node 2',
          x: 800,
          y: 300
        }, {
          name: 'Node 3',
          x: 550,
          y: 100
        }, {
          name: 'Node 4',
          x: 550,
          y: 500
        }],
        // links: [],
        links: [{
          source: 0,
          target: 1,
          symbolSize: [5, 20],
          label: {
            normal: {
              show: true
            }
          },
          lineStyle: {
            normal: {
              width: 5,
              curveness: 0.2
            }
          }
        }, {
          source: 'Node 2',
          target: 'Node 1',
          label: {
            normal: {
              show: true
            }
          },
          lineStyle: {
            normal: {curveness: 0.2}
          }
        }, {
          source: 'Node 1',
          target: 'Node 3'
        }, {
          source: 'Node 2',
          target: 'Node 3'
        }, {
          source: 'Node 2',
          target: 'Node 4'
        }, {
          source: 'Node 1',
          target: 'Node 4'
        }],
        lineStyle: {
          normal: {
            opacity: 0.9,
            width: 2,
            curveness: 0
          }
        }
      }
    ]
  };

}

chart4.ts

Chart4

Chart 5

The next chart displays a heatmap. The template page and TypeScript code are similar to the previous examples. Like in the second example you can interact with the legend.

<ion-header>
  <ion-toolbar>
    <ion-title>Chart 5</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <div echarts [options]="options" class="demo-chart"></div>
</ion-content>

chart5.html

import {Component} from '@angular/core';
import * as format from 'date-fns/format';

@Component({
  selector: 'page-chart5',
  templateUrl: 'chart5.html',
  styleUrls: ['chart5.scss']
})
export class Chart5Page {

  options = {
    tooltip: {
      position: 'top',
      formatter: function (p) {
        return p.data[0] + ': ' + p.data[1];
      }
    },
    visualMap: {
      min: 0,
      max: 1000,
      calculable: true,
      orient: 'horizontal',
      left: 'center',
      bottom: 0,
    },

    calendar: [{
      cellSize: [10, 'auto'],
      bottom: 45,
      orient: 'vertical',
      range: '2017',
      dayLabel: {
        margin: 5
      }
    }, {
      left: 250,
      cellSize: [10, 'auto'],
      bottom: 45,
      orient: 'vertical',
      range: '2018',
      dayLabel: {
        margin: 5
      }
    }],

    series: [{
      type: 'heatmap',
      coordinateSystem: 'calendar',
      calendarIndex: 0,
      data: this.getVirtulData(2017)
    }, {
      type: 'heatmap',
      coordinateSystem: 'calendar',
      calendarIndex: 1,
      data: this.getVirtulData(2018)
    }]
  };

  private getVirtulData(year) {
    year = year || '2017';
    const date = new Date(year, 0, 1).getTime();
    const end = new Date(year, 11, 31).getTime();
    const dayTime = 3600 * 24 * 1000;
    const data = [];
    for (let time = date; time <= end; time += dayTime) {
      data.push([
        format(time, 'YYYY-MM-DD'),
        Math.floor(Math.random() * 1000)
      ]);
    }
    return data;
  }

}

chart5.ts

Chart5

Chart 6

The last example shows you how to embed two charts into one page. In the template page we add two div tags with the echarts directive.

<ion-header>
  <ion-toolbar>
    <ion-title>Chart 6</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>
  <div echarts [options]="options1" [merge]="updateOptions1" class="demo-chart"></div>
  <div echarts [options]="options2" [merge]="updateOptions2" class="demo-chart"></div>
</ion-content>

chart6.html

In the SCSS file we use a media query to set the height of the div dependent on the device orientation. If the device is in landscape mode we set the height to 82 % height of the viewport and in portrait mode we set it to 40 % height of the viewport. With this style you see both charts in portrait mode and one chart in landscape mode.

@media screen and (orientation: portrait) {
  .demo-chart {
    width: 100vw;
    height: 40vh;
  }
}

@media screen and (orientation: landscape) {
  .demo-chart {
    width: 100vw;
    height: 82vh;
  }
}


chart6.scss

The TypeScript code defines the two configuration options (options1 and options2). When the user enters the page (ionViewWillEnter) the application starts a job that runs every second and generates random values. Like in the third example we use the merge option to update the chart with the new values.

import {Component} from '@angular/core';


@Component({
  selector: 'page-chart6',
  templateUrl: 'chart6.html',
  styleUrls: ['chart6.scss']
})
export class Chart6Page {
  private static oneDay = 24 * 3600 * 1000;

  data1: any = [];
  data2: any = [];
  updateOptions1: any;
  updateOptions2: any;
  options1 = {
    title: {
      text: 'Dynamic Data 1'
    },
    tooltip: {
      trigger: 'axis',
      formatter: function (params) {
        params = params[0];
        const date = new Date(params.name);
        return date.getDate() + '/' + (date.getMonth() + 1) + '/' + date.getFullYear() + ' : ' + params.value[1];
      },
      axisPointer: {
        animation: false
      }
    },
    xAxis: {
      type: 'time',
      splitLine: {
        show: false
      }
    },
    yAxis: {
      type: 'value',
      boundaryGap: [0, '100%'],
      splitLine: {
        show: false
      }
    },
    series: [{
      name: 'Sumulation Data',
      type: 'line',
      showSymbol: false,
      hoverAnimation: false,
      data: this.data1
    }]
  };
  options2 = {
    title: {
      text: 'Dynamic Data 2'
    },
    tooltip: {
      trigger: 'axis',
      formatter: function (params) {
        params = params[0];
        const date = new Date(params.name);
        return date.getDate() + '/' + (date.getMonth() + 1) + '/' + date.getFullYear() + ' : ' + params.value[1];
      },
      axisPointer: {
        animation: false
      }
    },
    xAxis: {
      type: 'time',
      splitLine: {
        show: false
      }
    },
    yAxis: {
      type: 'value',
      boundaryGap: [0, '100%'],
      splitLine: {
        show: false
      }
    },
    series: [{
      name: 'Sumulation Data',
      type: 'line',
      showSymbol: false,
      hoverAnimation: false,
      data: this.data2
    }]
  };
  private now = new Date(2017, 9, 3);
  private value = Math.random() * 1000;
  private randomDataInterval;

  constructor() {
    for (let i = 0; i < 1000; i++) {
      this.data1.push(this.randomData());
      this.data2.push(this.randomData());
    }
  }

  ionViewWillEnter() {
    this.randomDataInterval = setInterval(() => {
      for (let i = 0; i < 5; i++) {
        this.data1.shift();
        this.data1.push(this.randomData());
        this.data2.shift();
        this.data2.push(this.randomData());
      }

      this.updateOptions1 = {
        series: [{
          data: this.data1
        }]
      };

      this.updateOptions2 = {
        series: [{
          data: this.data2
        }]
      };
    }, 1000);
  }

  ionViewWillLeave() {
    clearInterval(this.randomDataInterval);
  }

  private randomData(): object {
    this.now = new Date(+this.now + Chart6Page.oneDay);
    this.value = this.value + Math.random() * 21 - 10;
    return {
      name: this.now.toString(),
      value: [
        [this.now.getFullYear(), this.now.getMonth() + 1, this.now.getDate()].join('/'),
        Math.round(this.value)
      ]
    };
  }

}

chart6.ts

chart6_1 chart6_2


You find the complete source code for the application on GitHub.