In this tutorial: Learn to Build Apache ECharts Dashboard with Angular

Apache ECharts is an open-source visualization library that is written in JavaScript. This library offers twenty different types of charts from basic ones, like pie, line, and bar charts, to more complex ones, like GEO/map, sankey, tree, and sunburst. It also bundles about a dozen utility components, like tooltip, brushing, and dataZoom that can be combined to augment the charts. It is optimized for mobile devices, prioritizes accessibility, and can adequately display large, intricate quantities of data.

In this tutorial, you will learn how to integrate Cube with an Angular 13 app. The app will use Apache ECharts to visualize query results from Cube. You will set up a database, add test data to it, run a Cube instance within a Docker container, and connect it to the database you made. Next, you will generate an Angular 13 app, add Apache ECharts to it, make queries to the Cube instance, and display the results using the ECharts. At the end of the tutorial, your Angular 13 app should look like this:

End Result: Apache Echart with Angular and Cube Apache Echart with Angular and Cube

More on Cube

Cube is an open-source analytical API platform. It helps you create data applications and reporting tools faster. This can be achieved either with a local instance or on the cloud. It acts as a headless API layer that frontend applications can use to interface with data warehouses, and it can connect to different types of data stores, like MongoDB, MySQL, and PostgreSQL. On its playground, you can create and test your projects, generate custom frontend interfaces, and analyze and improve your queries. Additionally, a secure REST API is made available for your apps to use when interacting with Cube. To learn more about Cube, you can head over to its website or read their documentation.

More on Apache ECharts with Angular

One of the best uses for Apache ECharts is displaying complex data that requires a more nuanced representation. With its wide range of charts, it can present a broad spectrum of data with varying characteristics. It is a great choice for handling substantial amounts of data as it is optimized for incremental rendering and data streaming.

Apache ECharts also has a large and active open-source community behind it that continually works to add to and improve it. ECharts implements the Web Accessibility Initiative - Accessible Rich Internet Applications (WAI-ARIA) specification, ensuring that users with disabilities can better access and interact with its charts. It is optimized for use on mobile devices; offers cross-platform support; and works with multi-dimensional, multi-format, and dynamic data.

A major downside of using ECharts is that it does not provide official libraries for various frontend web frameworks and mobile platforms, complicating the integration process.

Compared to other charting libraries, like Chart.js, however, ECharts offers a more extensive list of chart types, components, and features with a greater degree of customization.

It’s a fairly straightforward process to integrate ECharts with Cube. To start, you’d have to format the Cube query results into chart data that ECharts can accept. Then all that’s left to do is specify it as a property in the customization options for the chart, and ECharts handles the rest.

Tutorial: Building Apache Echart Dashboards with Angular

As explained above, this tutorial will walk you through how to integrate Cube with an Apache ECharts Angular 13 app. Let’s get started.

Use Case Theme

To demonstrate how to use Apache ECharts with Cube in this article, you will use college major graduate data from FiveThirtyEight. Specifically, you will use this women-stem.csv data, which you’ll download later on.

Prerequisites

To follow along with this tutorial, you will need the following:

  1. Node.js: You will use this to install the Angular CLI. This tutorial was made using Node.js v16.10.0. The Node.js Download page offers pre-built installers for various OSs.
  2. MongoDB Community Server: This will be the database you will use with Cube. This tutorial uses MongoDB v5.0.2. This manual provides instructions on how to install it for your particular OS.
  3. MongoDB Connector for BI: This is what Cube will use to interface with MongoDB. Cube uses SQL queries, and this connector translates them for MongoDB. You can find install instructions for your specific OS here.
  4. Angular CLI: You will use this to generate the Angular app. This tutorial was created using Angular CLI v13.1.2. The Angular CLI Documentation page will guide you through installing it.
  5. Docker Desktop: You will use Docker to run the containerized Cube.js app. This "Getting Started" guide on the Docker website will walk you through how to install it on your specific OS. This tutorial was made using Docker Desktop v 4.5.0.

Cube Setup

In this section, you will set up a Mongo database and user, run the MongoDB BI Connector process, and set up the Cube app. Cube will interface with the connector process to get data from your MongoDB database. This section shows you how to run a Cube instance within a Docker container. If you would like to run it in the cloud instead, you can follow these deployment guides available on Cube’s documentation site, or use Cube Cloud.

Step 1: Set Up a Mongo User

When setting up a Cube app, you’re required to provide information about the database you will connect to. This information includes a database host, port, username, and password. The user whose credentials you provide should have read access to your database.

In this step, you will create a user that the Cube app will use to access your database. To begin, start MongoDB. Refer to the installation instructions for your specific OS to do so. Once it’s started, launch the MongoDB Shell by running this:

mongosh

On the shell, set the current database to admin:

use admin

Then create a new user with the username cube and a password of your choosing. Run this command to create them and enter a suitable password:

db.createUser(
{
user: "cube",
pwd: passwordPrompt(),
roles: [ { role: "read", db: "collegeMajors" } ]
}
)

This command will give read permissions to the cube user on the collegeMajors database, which will hold the college majors data.

Ensure that MongoDB is running throughout the tutorial.

Step 2: Import Data into the Database

In this step, you will download the college majors of women in STEM data, women-stem.csv, from GitHub. You will then import this data to the collegeMajors database using mongoimport. Begin by creating a folder where the women-stem.csv data file, the Cube app, and the Angular app will reside:

mkdir college-majors && cd college-majors

In this folder, download the data from GitHub by running the following:

curl https://raw.githubusercontent.com/fivethirtyeight/data/master/college-majors/women-stem.csv --output women-stem.csv

Next, import the data into the collegeMajors database with this command:

mongoimport --type csv --db collegeMajors --collection womenStem --file women-stem.csv --headerline

All the data from the CSV file are now imported into the womenStem collection of the collegeMajors database.

Step 3: Run the Cube App

In this step, you will run the containerized Cube.js app. Begin by making sure that Docker is running. Next, within the college-majors folder, create a college-majors-cube folder where the Cube schemas and environment configuration will reside.

cd college-majors
mkdir college-majors-cube
cd college-majors-cube

Mac and Windows users can use this command to run Cube:

docker run --name my-first-cube \
-p 4000:4000 --rm \
-v ${PWD}:/cube/conf \
-e CUBEJS_DEV_MODE=true \
cubejs/cube

Linux users can run Cube with this command:

docker run --name my-first-cube \
-p 4000:4000 --rm \
--net=host
-v ${PWD}:/cube/conf \
-e CUBEJS_DEV_MODE=true \
cubejs/cube

The --name flag names the newly running container my-first-cube. You'll publish the container's 4000 port to the host's 4000 port. With the -rm flag, you specify that the container should automatically be removed when it exits. The -v flag mounts the current working directory (PWD) into the /cube/conf folder in the container. The -e flag will set the CUBEJS_DEV_MODE=true environment variable to ensure that Cube is running in development mode, which enables the Developer Playground, among other things. For Linux users, the –net flag connects the container to the host’s network so that it can connect to mongosqld.

Running this command will create two folders within college-majors-cube: schema and .cubestore. The schema is where all the schema files will be stored once you create them.

Step 4: Launch the MongoDB BI Connector mongosqld Process

In this step, you will start the MongoDB BI Connector process, mongosqld. The MongoDB BI Connector serves as a connection, which translates data and queries, between a MongoDB instance and an SQL business intelligence (BI) tool/client. These BI tools/clients are typically used for reporting on and visualizing data from the MongoDB database. One of its components, mongosqld, acts as a proxy that receives SQL queries from an SQL client/tool and relays them to a MongoDB instance.

While there are various ways to run mongosqld, you would typically start this process by running the command below on your terminal:

mongosqld

Leave this process running throughout the tutorial.

Step 5: Connect the MongoDB Database to the Cube App

Now that the mongosqld process is running, you can head to the Cube app. It can now be accessed at http://localhost:4000/ .

When you visit http://localhost:4000/ the first time you run the Cube app, you will be prompted to connect to a MongoDB database. This will happen on the http://localhost:4000/ page, which looks like this:

Setting up a database connection on Cube playground

Input these values on the form and then click the Apply button:

Mac and Windows Users:

Form ItemValue
Hostnamehost.docker.internal
Port (that mongosqld is running on)3307
Usernamecube
Password[The password you entered when creating the MongoDB user]
DatabasecollegeMajors

Linux Users:

Form ItemValue
Hostnamelocalhost
Port (that mongosqld is running on)3307
Usernamecube
Password[The password you entered when creating the MongoDB user]
DatabasecollegeMajors

Mac and Windows users will use host.docker.internal for the database hostname to connect the container with mongosqld on the host. This DNS name resolves to the internal IP address of the host.

Once the connection to the database is successfully made, you will be redirected to http://localhost:4000/#/schema, where you can view your Cube data schemas.

You’ve now successfully created a MongoDB database and user, added data to the database, set up the Cube app, and run it. In the next section, you’ll get a brief overview of the Cube playground, generate data schemas, and run some queries against the college majors’ data.

Cube Playground

The Cube playground is a web tool that allows you to generate data schemas for your data. With it, you can also run queries against this data and visualize the query results in various forms, like raw numbers, tables, and charts. The playground also provides a utility for generating analytics dashboards. These are built using a range of frontend frameworks, like React and Angular, among others.

Cube playground

The playground is split into three tabs: You build queries on the Build tab, generate analytics dashboards on the Dashboard App tab, and create data schemas on the Schema tab.

Cube playground tabs

During this portion of the tutorial, you will set up schemas and run a sample query on the playground.

Step 1: Create a WomenStem.js Schema

Head over to http://localhost:4000/#/schema. On the left-side navigation, under the Tables tab, you should see the collegeMajors database listed. Click on it and you should see the womenStem collection. Tick it and click the Generate Schema button.

Cube schema tables

You should see this pop-up message displayed once the schema is generated:

Successful schema generation message

The side navigation will then switch to the Files tab, which will show the newly generated WomenStem.js file.

Cube schema files

Step 2: Modify the WomenStem.js Schema

The WomenStem.js schema file is located at college-majors-cube/schema/WomenStem.js in the college-majors-cube folder. In this step, you will modify it to include additional measures. A measure is data you can quantify, such as the number of male and female graduates. A dimension is data you can categorize, like a major or a major category. The WomenStem.js schema currently has two measures: count, which is a type of measure that counts table records, and total, which corresponds to the total number of graduates. The WomenStem.js schema also has two dimensions: major and majorCategory.

You will add two measures to the WomenStem.js schema: women and men. They will be of the sum type, meaning that they will be a summation of the women and men fields. In the college-majors-cube/schema/WomenStem.js file, change the measures property:

measures: {
count: { type: `count`, drillMembers: [] },
total: { sql: `${CUBE}.\`Total\``, type: `sum` },
men: { sql: `${CUBE}.\`Men\``, type: `sum` },
women: { sql: `${CUBE}.\`Women\``, type: `sum` }
},

Step 3: Build a Query

You can use the Build tab to test out and experiment with queries. You can later use these queries in your frontend dashboard. For instance, if you wanted to model the number of female graduates in various major categories, you could easily do that on the playground. All you’d have to do is select women as a measure, major-category as a dimension, and a way to view the results. Here’s a screenshot of this query with its results displayed in a bar chart:

Cube sample playground query with annotations Cube sample playground query without annotations

In this section of the tutorial, you generated a WomenStem.js schema using data from the collegeMajors database, modified the schema to include additional measures, and built a query that you will use in the next phase.

Angular 13 Dashboard with Apache ECharts

In this last segment of the tutorial, you will generate an Angular 13 dashboard app, add the Apache ECharts library to it, and create charts that will visualize query results from the Cube app.

Step 1: Generate the Angular 13 Dashboard

To create the Angular 13 dashboard, start by changing directories to the college-majors folder and call the app college-majors-dashboard. To generate the app, run this command on your terminal:

ng new college-majors-dashboard --skip-tests --minimal

When prompted to add Angular routing, select No. Pick CSS for styling in the prompt that follows.

ng new college-majors-dashboard --minimal --skip-tests
Would you like to add Angular routing? No
Which stylesheet format would you like to use? CSS

Step 2: Add Environment Variables, Dependencies, and Imports

To connect to the college-majors-cube app, you will need to provide an API URL to it. If you are running in any other environment other than development, you also need to provide an API token that you will pass in an authorization header. Since the Cube app is running in development in this tutorial, you do not need to provide this token.

To add the Cube app API URL as an environment variable, replace the environment constant in the college-majors-dashboard/src/environments/environment.ts file with this instead:

export const environment = {
production: false,
cubeJSAPI: 'http://localhost:4000/cubejs-api/v1'
};

Apache ECharts does not provide an official Angular library, but a pretty reliable alternative is ngx-echarts, which is an Apache ECharts Angular directive. To install it, run the following command on your terminal within the college-majors-dashboard directory:

cd college-majors-dashboard
npm install echarts ngx-echarts -S

Running this command will add both Apache ECharts and the ngx-echarts directive to the app as dependencies.

Next, you will add three imports to AppModule: NgxEchartsModule, HttpClientModule, and FormsModule. NgxEchartsModule will provide the Apache ECharts while the FormsModule provides interactivity to the charts. HttpClientModule will provide an HTTP client to make requests to the Cube app. Change the contents of src/app/app.module.ts file to this:

import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { NgxEchartsModule } from 'ngx-echarts';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
NgxEchartsModule.forRoot({
echarts: () => import('echarts'),
}),
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

Step 3: Generate a College Majors Cube Service

While Cube provides an Angular module to integrate with Cube, it is not compatible with Angular 13, which is the current major version of Angular as of the writing of this tutorial. You may also face other compatibility issues with this module depending on what Angular version you use. So instead of using this, you will create an HTTP service that will make requests to the Cube app using its REST API.

The service will be called college-majors-cube. To create it, run the following command on your terminal:

ng generate service college-majors-cube

This will result in a file created at src/app/college-majors-cube.service.ts. Change the contents of this file:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { map, Observable } from 'rxjs';
import { environment } from 'src/environments/environment';
interface Query {
measures?: string[],
dimensions?: string[],
order?: object,
filters?: object[],
limit?: number
}
@Injectable({
providedIn: 'root'
})
class CollegeMajorsCubeService {
constructor(private http: HttpClient) { }
load(query: Query): Observable<object[]> {
return this.http.post<{ query: Query, data: object[] }>(`${environment.cubeJSAPI}/load`, { query })
.pipe(
map(resp => resp.data)
);
}
}
export { Query, CollegeMajorsCubeService };

In this service, HttpClient is injected into the constructor. The Query interface reflects what a Cube query looks like, for the most part. The load method makes a POST request to the Cube app with the query.

Step 4: Add AppComponent Methods to Format Chart Data and Make Queries

In this step, you’ll use AppComponent to make Cube queries and display their results using Apache ECharts. To do so, you will modify AppComponent.

To begin, change the contents of src/app/app.component.ts to the following:

import { Component, OnInit } from '@angular/core';
import { EChartsOption } from 'echarts';
import { map, Observable, reduce, switchMap } from 'rxjs';
import { CollegeMajorsCubeService, Query } from './college-majors-cube.service';
interface ChartData {
xAxisData: string[],
seriesData: number[][],
seriesLabels: string[]
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
constructor(private cmcs: CollegeMajorsCubeService) { }
ngOnInit() {}
}

Here, new imports are added at the top of the file, which are used later in the file. The ChartData interface models what the chart data should look like after it’s processed from the Cube query results. AppComponent’s templates and styling are changed from in-line to URLs. The CollegeMajorsCubeService is injected into the AppComponent’s constructor. You’ll use it to load queries from the Cube app. Lastly, AppComponent implements OnInit. You’ll use its ngOnInit method to make some initial queries.

Next, add a private formatBarChartData method to AppComponent:

private formatBarChartData(title = '', xAxisLabel = '', yAxisLabel = ''): (source$: Observable<ChartData>) => Observable<EChartsOption> {
return source$ => source$.pipe(
map(chartData => {
let options: EChartsOption = {
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
title: { text: title, show: true },
xAxis: { type: 'category', data: chartData.xAxisData, name: xAxisLabel, axisTick: { alignWithLabel: true } },
series: [],
yAxis: { type: 'value', name: yAxisLabel },
legend: { data: chartData.seriesLabels }
};
chartData.seriesData.forEach((series, index) => {
if (options.series && Array.isArray(options.series)) {
options.series.push({
type: 'bar',
data: series,
name: chartData.seriesLabels[index],
label: { show: true, rotate: 90, align: 'left', verticalAlign: 'middle', position: 'insideBottom', distance: 15, formatter: '{a} → {c}', fontSize: 14 }
});
}
});
return options;
})
);
}

The formatBarChartData method takes ChartData, creates an EChartOption, and adds the chart data to it. An EChartOption is used to set configuration for the chart and includes things like the chart title, its data, labels, and styling. In addition to adding chart data to options, this method also adds the title, xAxisLabel, and yAxisLabel arguments that it receives. Lastly, it adds chart styling, tooltips, and a legend, and then returns the EChart options.

Next, add a getChartOptions private method:

private getChartOptions(query: Query, title = '', xAxisLabel = '', yAxisLabel = '') {
return this.cmcs.load(query).pipe(
switchMap(data => data),
reduce((ac: ChartData, cv: object, index: number) => {
const vals = Object.values(cv);
if (index == 0) {
for (let i = 1; i < vals.length; i++) {
ac.seriesData.push([]);
}
ac.seriesLabels = Object.keys(cv).slice(1).map(k => k.substring(k.lastIndexOf('.') + 1));
}
ac.xAxisData.push(vals[0]);
for (let i = 1; i < vals.length; i++) {
ac.seriesData[i - 1].push(vals[i]);
}
return ac;
},
{ xAxisData: [], seriesData: [], seriesLabels: [] }
),
this.formatBarChartData(title, xAxisLabel, yAxisLabel)
);
}

The getChartOptions method calls the CollegeMajorsCubeService.load method using the query argument it receives. Once it receives the query results data, it reduces it to a ChartData object. This object is then passed to the formatBarChartData method, which returns an EChartOption object that an Apache EChart can use.

AppComponent will display four bar charts:

  1. Majors per category
  2. Graduates by gender in each major category
  3. Most popular majors in each gender group
  4. Most popular majors in each major category

To get data for the last two charts, you’ll need two public methods: getTopMajorsInGroup and getTopMajorsInCategory. selectedGroup and selectedCategory are properties that track which gender group and major category have been selected on the page. topGroupMajors$ and topCategoryMajors$ are observables that resolve the EChartOptions for the last two charts. Copy all these below and add them to AppComponent:

selectedGroup = 'total';
selectedCategory = 'Biology & Life Science';
topGroupMajors$!: Observable<EChartsOption>;
topCategoryMajors$!: Observable<EChartsOption>;
getTopMajorsInGroup() {
this.topGroupMajors$ = this.getChartOptions(
{
'measures': [
`WomenStem.${this.selectedGroup}`
],
'order': {
[`WomenStem.${this.selectedGroup}`]: 'desc'
},
'dimensions': [
'WomenStem.major'
],
'limit': 3
},
`Popular Majors in ${this.selectedGroup}`,
'Major',
'Number of Graduates'
);
}
getTopMajorsInCategory() {
this.topCategoryMajors$ = this.getChartOptions(
{
'measures': ['WomenStem.women', 'WomenStem.men', 'WomenStem.total'],
'order': { 'WomenStem.total': 'desc' },
'dimensions': ['WomenStem.major'],
'filters': [
{
'member': 'WomenStem.majorCategory',
'operator': 'equals',
'values': [this.selectedCategory]
}
],
'limit': 3
},
`Graduates in Top 3 Popular Majors in ${this.selectedCategory}`,
'Majors',
'Number of Graduates'
);
}
ngOnInit() {
this.getTopMajorsInGroup();
this.getTopMajorsInCategory();
}

Here, getTopMajorsInGroup calls the getChartOptions method with a query containing a gender group measure and a major dimension. It then assigns the resultant EChartOption observable to topGroupMajors$. getTopMajorsInCategory calls the getChartOptions method with a query containing all the gender groups as a measure and major as a dimension, and then filters the results by the selected major category. It assigns the EChartOption observable it gets to the topCategoryMajors$. Currently, the charts that use the options returned by topGroupMajors$ and topCategoryMajors$ are not displayed when the page loads; they are only shown when selections are made from the drop-downs. To remedy this, you’ll call both the getTopMajorsInGroup and getTopMajorsInCategory methods in the ngOnInit method.

The first two charts have their options held by the majorsPerCategory$ and majorCategoriesByGender$ properties. Copy them below and add them to AppComponent:

majorsPerCategory$ = this.getChartOptions(
{
'measures': ['WomenStem.count'],
'dimensions': ['WomenStem.majorCategory']
},
'Majors Per Category',
'Major Categories',
'Number of Majors'
);
majorCategoriesByGender$ = this.getChartOptions(
{
'measures': ['WomenStem.women', 'WomenStem.men', 'WomenStem.total'],
'dimensions': ['WomenStem.majorCategory']
},
'Graduates per Major Category by Gender',
'Major Categories',
'Number of Graduates'
);

Here, majorsPerCategory$ makes a query measuring the number of majors per major category, while majorCategoriesByGender$ measures the number of graduates per gender group in each major category.

Step 5: Add Template Content and Styling

In this step, you will add content to the AppComponent template and style files. Start by creating these files on your terminal using the command below:

touch src/app/app.component.html src/app/app.component.css

Add this code to the src/app/app.component.html file:

<h1>Apache ECharts with Cube.js Query Results</h1>
<h2>Bar Chart 1</h2>
<div *ngIf="majorsPerCategory$ | async as majorsPerCategory" echarts [options]="majorsPerCategory" class="cm-bar-chart">
</div>
<hr>
<h2>Bar Chart 2</h2>
<div *ngIf="majorCategoriesByGender$ | async as majorCategoriesByGender" echarts [options]="majorCategoriesByGender"
class="cm-bar-chart">
</div>
<hr>
<h2>Bar Chart 3</h2>
<label for="group">Select a group to show popular majors</label>
<select [(ngModel)]="selectedGroup" (ngModelChange)="getTopMajorsInGroup()" id="group" name="group">
<option *ngFor="let gr of ['women', 'men', 'total']" [ngValue]="gr">{{gr}}</option>
</select>
<div *ngIf="topGroupMajors$ | async as topGroupMajors" echarts [options]="topGroupMajors" class="cm-bar-chart">
</div>
<hr>
<h2>Bar Chart 4</h2>
<label for="group">Select a category to show popular majors</label>
<select [(ngModel)]="selectedCategory" (ngModelChange)="getTopMajorsInCategory()" id="group" name="group">
<option
*ngFor="let cat of ['Health', 'Engineering', 'Biology & Life Science', 'Computers & Mathematics', 'Physical Sciences']"
[ngValue]="cat">{{cat}}</option>
</select>
<div *ngIf="topCategoryMajors$ | async as topCategoryMajors" echarts [options]="topCategoryMajors" class="cm-bar-chart">
</div>

In this file, all four charts are included, and the chart options for them are specified. Two selects are also added so that new queries can be made for each different gender group or major category selected for the “Most popular majors in each gender group” and “Most popular majors in each major category” charts.

Lastly, add styling to src/app/app.component.css:

.cm-bar-chart {
min-width: 90vw;
padding: 1rem;
margin: 3rem 1rem;
border: 1px solid lightgray;
}
h1, h2 {
margin: 3rem 1rem 0 1rem;
font-family: Arial;
}
select, label {
font-family: Arial;
font-size: 1.5rem;
padding: 0 1rem;
}

Step 6: Preview the Dashboard

Now that you’ve finished adding all the code you need for the dashboard, you can start the app. In your terminal, run the following command:

ng serve

The college majors dashboard will be served at http://localhost:4200/. Check it out in your browser.

Conclusion

In this tutorial, you created a database, added sample data to it, generated a Cube app, and connected the database to it. You then created a data schema for the sample data, modified it to include additional measures, and made a test query on the Cube playground. Finally, you generated an Angular 13 app, added Apache ECharts to it, created a service to interface with the Cube REST API, and added four charts to visualize query results.

Please don't hesitate to like and bookmark this post, write a comment, and give a star to Cube on GitHub. I hope that you'll try Cube, Apache ECharts, and Angular in your next production gig or your next pet project.