Building an Apache ECharts Dashboard with Angular and Cube
Tags: javascript, angular, metric, webdev
Published: 2022-03-10 14:36:20

This guest post was written by Zara Cooper. She is a web developer and technical writer. You can get in touch with her through her [website](https://zaracooper.github.io/).

---

[Apache ECharts](https://echarts.apache.org/) 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](https://echarts.apache.org/examples/en/index.html). It also bundles [about a dozen utility components, like tooltip, brushing, and dataZoom](https://echarts.apache.org/en/feature.html#interaction) that can be combined to augment the charts. It [is optimized for mobile devices](https://echarts.apache.org/en/feature.html#mobile), [prioritizes accessibility](https://echarts.apache.org/en/feature.html#aria), and can [adequately display large, intricate quantities of data](https://echarts.apache.org/en/feature.html#big-data).

In this tutorial, you will learn how to integrate [Cube](https://cube.dev/) with an [Angular 13](https://angular.io/) 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:

![Apache ECharts Angular 13 Dashboard](https://i.imgur.com/Hd8uyJk.png)

## More on Cube

[Cube](https://cube.dev/) 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](https://cube.dev/) or [read their documentation](https://cube.dev/docs).

## More on Apache ECharts

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](https://echarts.apache.org/en/feature.html#visual-mapping). It is a great choice for handling substantial amounts of data as it is [optimized for incremental rendering and data streaming](https://echarts.apache.org/en/feature.html#big-data).

Apache ECharts also has a [large and active open-source community behind it](https://github.com/apache/echarts) that continually works to add to and improve it. ECharts [implements the Web Accessibility Initiative - Accessible Rich Internet Applications (WAI-ARIA) specification](https://echarts.apache.org/en/feature.html#aria), ensuring that users with disabilities can better access and interact with its charts. It is [optimized for use on mobile devices](https://echarts.apache.org/en/feature.html#mobile); [offers cross-platform support](https://echarts.apache.org/en/feature.html#mult-platform); and works with [multi-dimensional](https://echarts.apache.org/en/feature.html#visual-mapping), [multi-format](https://echarts.apache.org/en/feature.html#dataset), and [dynamic data](https://echarts.apache.org/en/feature.html#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](https://www.chartjs.org/), 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

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](https://github.com/fivethirtyeight/data/tree/master/college-majors). Specifically, you will use [this `women-stem.csv` data](https://github.com/fivethirtyeight/data/blob/master/college-majors/women-stem.csv), which you’ll download later on.

### Prerequisites

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

1. **[Node.js](https://nodejs.org/en/):** You will use this to install the Angular CLI. This tutorial was made using Node.js v16.10.0. The Node.js [**Download** page](https://nodejs.org/en/download/) offers pre-built installers for various OSs.
2. **[MongoDB Community Server](https://www.mongodb.com/try/download/community):** This will be the database you will use with Cube. This tutorial uses MongoDB v5.0.2. [This manual](https://docs.mongodb.com/manual/administration/install-community/) provides instructions on how to install it for your particular OS.
3. **[MongoDB Connector for BI](https://www.mongodb.com/try/download/bi-connector):** 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](https://docs.mongodb.com/bi-connector/master/installation/#installation-guides).
4. **[Angular CLI](https://angular.io/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](https://angular.io/cli#installing-angular-cli) will guide you through installing it.
5. **[Docker Desktop](https://www.docker.com):** You will use Docker to run the containerized Cube.js app. This ["Getting Started" guide on the Docker website](https://www.docker.com/get-started) 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](https://cube.dev/docs/deployment/overview), or use [Cube Cloud](https://cube.dev/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](https://docs.mongodb.com/manual/administration/install-community/) to do so. Once it’s started, launch the [MongoDB Shell](https://docs.mongodb.com/mongodb-shell/) by running this:

```bash
mongosh
```

On the shell, set the current database to `admin`:

```bash
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:

```bash
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](https://github.com/fivethirtyeight/data/blob/master/college-majors/women-stem.csv). You will then import this data to the `collegeMajors` database using `mongoimport`. Begin by creating a folder where [the `women-stem.csv` data file](https://github.com/fivethirtyeight/data/blob/master/college-majors/women-stem.csv), the Cube app, and the Angular app will reside:

```bash
mkdir college-majors && cd college-majors 
```

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

```bash
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:

```bash
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. 

```bash
cd college-majors
mkdir college-majors-cube
cd college-majors-cube
```

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

```bash
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:

```bash
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](https://cube.dev/docs/configuration/overview#development-mode), which [enables the Developer Playground](https://cube.dev/docs/dev-tools/dev-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`](https://docs.mongodb.com/bi-connector/current/reference/mongosqld/). The [MongoDB BI Connector](https://docs.mongodb.com/bi-connector/current/what-is-the-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`](https://docs.mongodb.com/bi-connector/current/launch/#launching-mongosqld), you would typically start this process by running the command below on your terminal:

```bash
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](http://localhost:4000).

When you visit [http://localhost:4000](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/#/connection](http://localhost:4000/#/connection) page, which looks like this:

![Setting up a database connection on Cube playground](https://i.imgur.com/eQWBfN7.png)

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

**Mac and Windows Users:**

Form Item | Value
--- | --- 
**Hostname** | `host.docker.internal` 
**Port** (that `mongosqld` is running on) | `3307`
**Username** | `cube`
**Password** | _[The password you entered when creating the MongoDB user]_
**Database**| `collegeMajors`

**Linux Users:**

Form Item | Value
--- | --- 
**Hostname** | `localhost` 
**Port** (that `mongosqld` is running on) | `3307`
**Username** | `cube`
**Password** | _[The password you entered when creating the MongoDB user]_
**Database**| `collegeMajors`

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](http://localhost:4000/#/schema), where you can view your [Cube data schemas](https://cube.dev/docs/schema/getting-started).

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](https://cube.dev/docs/dev-tools/dev-playground) is a web tool that allows you to generate [data schemas](https://cube.dev/docs/schema/getting-started) 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](https://reactjs.org/) and Angular, among others.

![Cube playground](https://i.imgur.com/MZhAB3p.png)

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](https://i.imgur.com/oif5vgB.png)

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](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](https://i.imgur.com/XuCCMIj.png)

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

![Successful schema generation message](https://i.imgur.com/q9Ja1VM.png)

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

![Cube schema files](https://i.imgur.com/vKhpCOG.png)

#### 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*](https://cube.dev/docs/schema/fundamentals/concepts#measures) is data you can quantify, such as the number of male and female graduates. A [*dimension*](https://cube.dev/docs/schema/fundamentals/concepts#dimensions) is data you can categorize, like a major or a major category. The `WomenStem.js` schema currently has two measures: [`count`](https://cube.dev/docs/schema/reference/types-and-formats#measures-types-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`](https://en.wikipedia.org/wiki/Academic_major) and `majorCategory`.

You will add two measures to the `WomenStem.js` schema: `women` and `men`. They will be of the [`sum`](https://cube.dev/docs/schema/reference/types-and-formats#measures-types-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:

```javascript
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](https://i.imgur.com/LHZX1tj.png)
![Cube sample playground query without annotations](https://i.imgur.com/Wr9f6bu.png)

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:

```bash
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.

```bash
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](https://cube.dev/docs/rest-api#prerequisites-authentication). 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:

```typescript
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](https://www.npmjs.com/package/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:

```bash
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:

```typescript
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](https://cube.dev/docs/@cubejs-client-ngx) 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](https://github.com/cube-js/cube.js/issues?q=is%3Aissue+is%3Aopen+angular). So instead of using this, you will create an HTTP service that will make requests to the Cube app [using its REST API](https://cube.dev/docs/rest-api/).

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

```bash
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:

```typescript
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:

```typescript
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`:

```typescript
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](https://echarts.apache.org/en/option.html#title). 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:

```typescript
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 `EChartOption`s for the last two charts. Copy all these below and add them to `AppComponent`:

```typescript
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`:

 ```typescript
  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:

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

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

```html
<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`:

```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:

```bash
ng serve
```

The college majors dashboard will be served at [http://localhost:4200](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](https://github.com/cube-js/cube.js) on GitHub. I hope that you'll try Cube, Apache ECharts, and Angular in your next production gig or your next pet project.

The code for the frontend portion of this app can be found in [this GitHub repo](https://github.com/zaracooper/college-majors-dashboard/). The Cube `WomenStem.js` schema file can be found in [this gist](https://gist.github.com/zaracooper/9d21901c7854acf0c7c9db0efaf07ebd). The [college majors of women in STEM data, `women-stem.csv`,](https://github.com/fivethirtyeight/data/blob/master/college-majors/women-stem.csv) by [FiveThirtyEight](https://github.com/fivethirtyeight) is licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). This data was not modified.