Building an Apache ECharts Dashboard with Vue 3 and Cube

adnanrahic

Adnan Rahić

Posted on February 10, 2022

Building an Apache ECharts Dashboard with Vue 3 and Cube

This guest post was written by Ashutosh Singh.
Ashutosh is a writer, learner, and JavaScript developer who enjoys writing articles that help people.
You can get in touch with him through his website or on Twitter!


Apache ECharts is a powerful charting and visualization library. When paired with an analytics API like Cube, you can build some incredibly compelling dashboards.

In this tutorial, you’ll learn how to create a data visualization dashboard using Vue.js and Cube, a Headless BI and analytics APIs for building data applications. You’ll also learn how you can use Apache ECharts, an open source JavaScript visualization library, to create charts and visual designs. When you’re finished, you’ll have created an application like the one below.

Final app

If you want to jump right into the code, you can check out the GitHub repo and follow along. You also have access to the deployed version.

What Is Cube?

Cube is an open source analytics framework used for building high-performance data applications. With Cube, you can connect your data warehouses like MongoDB, PostgreSQL, and Snowflake to your frontend application like Vue.js to build data applications like real-time dashboards faster and more flexibly than other charting and visualization libraries.

What Is Apache ECharts?

Apache ECharts

Apache Echarts is a powerful, interactive charting and data visualization library that can be used with most browsers to create stunning charts, visual designs, graphs, and more.

Apache Echarts is often used with data and monitoring tools to create custom charts that can provide professional data analysis in an elegant design. When compared to its alternatives like D3.js, ECharts is more powerful, easy to adapt, and has better performance.

Here are just a few reasons why you should use Apache ECharts:

  • It supports tons of charts like line series, bar series, scatter series, pie charts, candlestick series, boxplot series for statistics, treemap series, sunburst series, parallel series for multidimensional data, funnel series, and gauge series. It’s also incredibly easy to combine and create new charts.
  • You can toggle between canvas and SVG rendering without any hassle.
  • It supports multidimensional analyses of data sets with data transformations like filtering, clustering, and regression.
  • It helps you create stunning, responsive, and highly customisable designs.
  • With over 50,000 stars on GitHub, ECharts has one of the most active open source communities, ensuring the healthy development of the project and tons of resources to take inspiration from.
  • ECharts is optimized for mobile interaction.

The Museum of Modern Art Data Set

The Museum of Modern Art (MoMA) collection data is a public data set available in JSON and CSV formats. It’s published and maintained by MoMA and contains around 140,000 records. You can use this data set for your side projects and demo applications since it’s available with a CC0 License.

In this tutorial, you’ll use this data set to create a dashboard with charts and tables using Cube, Apache ECharts, and Vue.js.

How to Set Up Cube

Before you begin, you’ll need the following:

  • Knowledge of HTML, CSS, and JavaScript.
  • Basic knowledge of Vue.js.
  • Node and npm installed on your local dev machine.
  • Any code editor of your choice like Visual Studio Code.

In this section, you’ll learn how to set up Cube on your local machine and how to scaffold a new Cube project using the Cube CLI tool.

The Cube command-line interface (CLI) can be used to quickly create a new Cube service and generate schemas based on your database tables.

Run the following command in the terminal to install the Cube CLI on your machine:

npm install -g cubejs-cli
Enter fullscreen mode Exit fullscreen mode

Next, you need to create a new Cube service. To do that, run the following command in the terminal of your project’s root:

npx cubejs-cli create vue-echarts -d postgres
Enter fullscreen mode Exit fullscreen mode

In this tutorial, you’ll be using a Postgres instance of the MoMA data set, but you can use any other database like MySQL or Oracle. You can find more about all the available databases on Cube’s website.

The last command above creates a new project named vue-echarts with the following folder structure:

├── cube.js
├── docker-compose.yml
├── package-lock.json
├── package.json
└── schema
    └── Orders.js
Enter fullscreen mode Exit fullscreen mode

Orders.js is a sample schema created by the Cube CLI tool with sample data.

cube(`Orders`, {
  sql: `
  select 1 as id, 100 as amount, 'new' status
  UNION ALL
  select 2 as id, 200 as amount, 'new' status
  UNION ALL
  select 3 as id, 300 as amount, 'processed' status
  UNION ALL
  select 4 as id, 500 as amount, 'processed' status
  UNION ALL
  select 5 as id, 600 as amount, 'shipped' status
  `,

  preAggregations: {
    // Pre-Aggregations definitions go here
    // Learn more here: https://cube.dev/docs/caching/pre-aggregations/getting-started
  },

  measures: {
    count: {
    type: `count`
    },

    totalAmount: {
    sql: `amount`,
    type: `sum`
    }
  },

  dimensions: {
    status: {
    sql: `status`,
    type: `string`
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

The vue-echarts project will contain a .env file with the Cube service credentials. The .env file will look like this:

# Cube.js environment variables: https://cube.dev/docs/reference/environment-variables
CUBEJS_DEV_MODE=true
CUBEJS_DB_TYPE=postgres
CUBEJS_API_SECRET=1bea38a48b6e92af20a7026bdb29893ce6fadb1d76edad085121f326acb7ccf0c5077ff7242af7cf8f7afc0ba5420bcb464e384c4721aeb94d54e05ed1975f30
CUBEJS_EXTERNAL_DEFAULT=true
CUBEJS_SCHEDULED_REFRESH_DEFAULT=true
CUBEJS_WEB_SOCKETS=true
Enter fullscreen mode Exit fullscreen mode

Copy the CUBEJS_API_SECRET key; you’ll use this later to connect to the Cube cluster from the Vue.js app.

Update the .env file to include the credential keys that connect to the Postgres instance made with the MoMA data set.

CUBEJS_DB_TYPE=postgres
CUBEJS_DB_HOST=demo-db-examples.cube.dev
CUBEJS_DB_NAME=moma
CUBEJS_DB_USER=cube
CUBEJS_DB_PASS=12345
Enter fullscreen mode Exit fullscreen mode

You can also connect to a local instance of Postgres by replacing the above credentials with your local Postgres instance’s credentials.

How to Generate Schema

Next, you’ll generate schema files using Cube Developer Playground.

A Cube Data Schema models raw data into meaningful business definitions. It also pre-aggregates data for optimal results. The data schema is exposed through the querying API, allowing end users to query a wide variety of analytical queries without modifying the schema itself.

Run the following command in your terminal:

cd vue-echarts
npm run dev
Enter fullscreen mode Exit fullscreen mode

You’ll see the Cube Developer Playground showing all the connected database tables.

Navigate to localhost:4000 in your browser. There, you’ll see the Cube Developer Playground showing all the connected database tables. Under Tables, select public and click on Generate Schema.

Cube Developer Playground

Once the schema is generated successfully, you’ll see a pop-up like this:

Cube Developer Playground pop-up

Next, click on Build and create your first query. Once you’ve created a query similar to the one shown below, select Run.

Query1

The above query returns all the works of art present in the MoMA data set.

Query1-Result

You can click on the JSON Query tab to get the query you created in a JSON format, which you’ll use later in the Vue project to run queries from the Vue.js frontend.

The final JSON query will look like this:

Query1 JSON format

Next, you’ll create the query to get the status of orders present in the Orders sample schema.

Click on the + icon next to Query 1 on the Cube Developer Playground to create another query to get the status of the orders.

Query2

Like the first query, you can get the query above in JSON format by clicking on the JSON Query button.

Query2 JSON format

How to Install and Set Up a Vue.js Project

In this section, you’ll learn how to set up and install Vue.js. You’ll also see how you can run the queries created in the last section from the Vue.js app.

In this tutorial, you’ll use Vue CLI to create the initial Vue.js project. To install Vue CLI, run the following command:

npm install -g @vue/cli
Enter fullscreen mode Exit fullscreen mode

Next, create a Vue.js project named vue-cube-echarts by running this command in your project’s root terminal:

vue create vue-cube-echarts
Enter fullscreen mode Exit fullscreen mode

When prompted to choose the preset, choose Default (Vue 3) ([Vue 3] babel, eslint).

Vue CLI v4.5.15
? Please pick a preset:
  Default ([Vue 2] babel, eslint)
> Default (Vue 3) ([Vue 3] babel, eslint)
  Manually select features

Enter fullscreen mode Exit fullscreen mode

After the project has been created, you need to start the development server with the following command:

cd vue-cube-echarts
npm run serve
Enter fullscreen mode Exit fullscreen mode

Navigate to http://localhost:8080/ in your browser. Now your app should look like this:

Vue.js app

The next step is to install the Cube Vue client. Run the following command in the terminal to install @cubejs-client/core and @cubejs-client/vue3:

npm install @cubejs-client/core @cubejs-client/vue3
Enter fullscreen mode Exit fullscreen mode

Update the App.vue file:

<template>
  <h1>Vue Cube.js ECharts</h1>
  <div class="dashboard">
    <div class="cards-container">
    <query-builder :cubejs-api="cubejsApi" :query="totalPaintingsQuery">
        <template v-slot="{ loading, resultSet }">
        <div v-if="loading" class="loading">Loading...</div>
        <div v-if="!loading && resultSet !== undefined">
            <Card :resultSet="resultSet" title="Total Paintings" />
        </div>
        </template>
    </query-builder>
            <query-builder :cubejs-api="cubejsApi" :query="orderStatusQuery">
        <template v-slot="{ loading, resultSet }">
        <div v-if="loading" class="loading">Loading...</div>
        <div v-if="!loading && resultSet !== undefined">
            <OrderCard :resultSet="resultSet" title="Order Status" />
        </div>
        </template>
    </query-builder>
    </div>
    </div>
</template>

<script>
import cubejs from "@cubejs-client/core";
import { QueryBuilder } from "@cubejs-client/vue3";
import Card from "./components/Card";
import OrderCard from "./components/OrderCard";

const cubejsApi = cubejs(
  "1bea38a48b6e92af20a7026bdb29893ce6fadb1d76edad085121f326acb7ccf0c5077ff7242af7cf8f7afc0ba5420bcb464e384c4721aeb94d54e05ed1975f30",
  {
    apiUrl: "http://localhost:4000/cubejs-api/v1",
  }
);

export default {
  name: "App",
  components: {
    QueryBuilder,
    Card,
    OrderCard,
  },
  data() {
    return {
    cubejsApi,
    totalPaintingsQuery: {
        measures: ["Artworks.count"],
    },
    orderStatusQuery: {
        measures: ["Orders.count"],
        timeDimensions: [],
        order: {
        "Orders.count": "desc",
        },
        filters: [],
        dimensions: ["Orders.status"],
    },
    };
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  padding: 0 3rem;
  margin-top: 30px;
}
.dashboard {
  display: flex;
  justify-content: space-evenly;
  gap: 1rem;
  flex-wrap: wrap;
  align-items: center;
}
.cards-container {
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
  gap: 1rem;
  flex-wrap: wrap;
  align-items: center;
}
.card-wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: fit-content;
  min-width: 250px;
  min-height: 100px;
  padding: 2rem;
  border-radius: 5px;
  background-color: #fafafa;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
}
.card-title {
  font-size: 1.2rem;
  font-weight: bold;
  margin: 0;
}
.card-content {
  font-size: 2.5rem;
  font-weight: bold;
  color: #42b983;
  padding-top: 6px;
}
</style>
Enter fullscreen mode Exit fullscreen mode

In the code above, you import cubejs from @cubejs-client/core and QueryBuilder from @cubejs-client/vue3, respectively. Then you initialize a new instance of Cube by pasting the CUBEJS_API_SECRET copied in the last section.

const cubejsApi = cubejs(
  "1bea38a48b6e92af20a7026bdb29893ce6fadb1d76edad085121f326acb7ccf0c5077ff7242af7cf8f7afc0ba5420bcb464e384c4721aeb94d54e05ed1975f30",
  {
    apiUrl: "http://localhost:4000/cubejs-api/v1",
  }
);
Enter fullscreen mode Exit fullscreen mode

In the code above, you pass the QueryBuilder as a component in the components section used in the template syntax.

Below, you’ll paste the queries for getting all the artwork and orders status created in the last section and copied from the JSON Query tab.

data() {
    return {
    cubejsApi,
    totalPaintingsQuery: {
        measures: ["Artworks.count"],
    },
    orderStatusQuery: {
        measures: ["Orders.count"],
        timeDimensions: [],
        order: {
        "Orders.count": "desc",
        },
        filters: [],
        dimensions: ["Orders.status"],
    },
    };
}
Enter fullscreen mode Exit fullscreen mode

In the query-builder component, you pass the cubejs instance and the query to be executed. The result of the executed query is passed to the child components as a resultSet object.

<query-builder :cubejs-api="cubejsApi" :query="totalPaintingsQuery">
  <template v-slot="{ loading, resultSet }">
    <div v-if="loading" class="loading">Loading...</div>
    <div v-if="!loading && resultSet !== undefined">
    <Card :resultSet="resultSet" title="Total Paintings" />
    </div>
  </template>
</query-builder>
Enter fullscreen mode Exit fullscreen mode

The next step is to create the components that take this resultSet object and show the corresponding result in the app.

Under src/components, run the following command to create two new files named Card.vue and OrderCard.vue in the Vue project terminal:

touch src/components/Card.vue
touch src/components/OrderCard.vue
Enter fullscreen mode Exit fullscreen mode

Add the following code to the Card.vue file:

<template>
  <div class="card-wrapper">
    <h2 class="card-title">
    {{ title }}
    </h2>
    <span class="card-content">
    {{ count }}
    </span>
  </div>
</template>

<script>
export default {
  name: "Card",
  props: {
    title: String,
    resultSet: Object,
  },
  data() {
    return {
    count: this.resultSet
        .rawData()
        .map((item) => Object.values(item).map((value) => value))
        .join(" "),
    };
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

In the code above, you pass the resultSet as a prop to the Card component and parse it to display just the number of artwork available in the data set using the rawData() method on the resultSet object. The rawData returns the data in its raw form. More information on the rawData() method can be found in Cube’s docs.

Above, you also parse the array to extract the value of the Artworks.count property using the map and Object.values() methods.

Add the following code to OrderCard.vue:

<template>
  <div class="card-wrapper">
    <h2 class="card-title">
    {{ title }}
    </h2>
    <div class="card-content-wrapper">
    <div v-for="(item, index) in items" :key="index" class="card-content">
        <span class="status">
        {{ item[0].toUpperCase() }}
        </span>
        <span class="count">
        {{ item[1] }}
        </span>
    </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "OrderCard",
  props: {
    title: String,
    resultSet: Object,
  },
  data() {
    return {
    items: this.resultSet
        .rawData()
        .map((item) => Object.values(item).map((element) => element)),
    };
  },
};
</script>

<style scoped>
.card-content-wrapper {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
}
.card-content {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.status {
  font-weight: bold;
  font-size: 1rem;
  color: darkseagreen;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Like Card.vue, you use the rawData() method on the resultSet object and parse the result to show the required values.

To start your Cube dev server, run the following command in the project’s root directory:

cd vue-echarts
npm run dev
Enter fullscreen mode Exit fullscreen mode

Now, run the following commands to start your Vue development server:

cd vue-cube-echarts
npm run serve
Enter fullscreen mode Exit fullscreen mode

Navigate to http://localhost:8080/ in your browser, and your app should look like this:

Vue-Cube Echarts

The two components above show a particular analytics value: the total number of sales, the total number of users, orders’ status, orders processed, etc.

How to Create a Table Using Cube

In this section, you’ll create the query and the component to show a list of data in a tabular form. In a production app, you can use this component or query to display the list of active orders, vendors, a list of deliveries in a region, and more.

Head over to http://localhost:4000/ in the browser (i.e., the Cube Playground), and create a third query to get a list of artists with more than 1,000 works of art and their name doesn’t contain the word Unknown. You’ll use the set filter operator to check whether the value of the member is not NULL. It’s also helpful to remove null entries from the result.

Vue-Cube-TableQuery

Similar to the queries above, you can click on Run to run this query and JSON Query to get the query in JSON format.

Update your App.vue file and add the following code in the template section right after the cards-container div:

<div class="table-container">
    <query-builder :cubejs-api="cubejsApi" :query="artistQuery">
        <template v-slot="{ loading, resultSet }">
        <div v-if="loading" class="loading">Loading...</div>
        <div v-if="!loading && resultSet !== undefined">
            <Table v-if="!loading" :resultSet="resultSet" />
        </div>
        </template>
    </query-builder>
</div>
Enter fullscreen mode Exit fullscreen mode

In the script section, first import the Table component which you'll be creating in a second:

import Table from "./components/Table";
Enter fullscreen mode Exit fullscreen mode

Add the Table component to the list of components:

export default {
  name: "App",
    components: {
        QueryBuilder,
        Card,
        OrderCard,
        Table,
  },
  ...
}
Enter fullscreen mode Exit fullscreen mode

Finally, add a new query named artistQuery to the return value of the data() function:

artistQuery:
{
  "measures": ["Artworks.count"],
  "timeDimensions": [],
  "order": {
    "Artworks.count": "desc"
  },
  "dimensions": ["Artworks.artist"],
  "filters": [
    {
    "member": "Artworks.count",
    "operator": "gte",
    "values": ["1000"]
    },
    {
    "member": "Artworks.artist",
    "operator": "set"
    },
    {
    "member": "Artworks.artist",
    "operator": "notContains",
    "values": ["Unknown"]
    }
  ]
},
Enter fullscreen mode Exit fullscreen mode

Create a new file named Table.vue under the components directory by running the following command:

touch src/components/Table.vue
Enter fullscreen mode Exit fullscreen mode

Add the following Table.vue code:

<template>
  <div v-if="!loading" class="wrapper">
    <div class="table-header">
    <div class="col-1">Artist</div>
    <div class="col-2">Number of Paintings</div>
    </div>
    <div class="table-body" v-for="(item, index) in items" :key="index">
    <span class="col-1">
        {{ item[0] }}
    </span>
    <span class="col-2">
        {{ item[1] }}
    </span>
    </div>
  </div>
</template>

<script>
export default {
  name: "Table",
  props: {
    resultSet: Object,
  },
  computed: {
    items: function () {
    return this.resultSet
        .rawData()
        .map((item) => Object.values(item).map((value) => `${value}`));
    },
  },
};
</script>

<style scoped>
.table-header,
.table-body {
  display: flex;
  width: 800px;
}
.col-1 {
  flex: 1;
}
.col-2 {
  flex: 0 0 43%;
}
.table-header {
  background-color: #f5f5f5;
  font-size: 1.4rem;
  font-weight: bold;
}
.table-body {
  background-color: #fff;
  font-size: 1.2rem;
  padding-bottom: 5px;
  border-bottom: 1px solid #ddd;
}
.table-body > .col-2 {
  color: #42b983;
}
</style>
Enter fullscreen mode Exit fullscreen mode

In the code above, you use the rawData() method to get the data from the resultSet object and parse it to render it in a tabular form.

The data returned from the rawData() method will look like this:

[
  { "Artworks.artist": "Eugène Atget", "Artworks.count": "5050" },
  { "Artworks.artist": "Louise Bourgeois", "Artworks.count": "3335" },
  { "Artworks.artist": "Ludwig Mies van der Rohe", "Artworks.count": "2645" },
  ...
];
Enter fullscreen mode Exit fullscreen mode

Your code will look like the following after parsing it with the map and Object.values() method:

[
  ["Eugène Atget", "5050"],
  ["Louise Bourgeois", "3335"],
  ["Ludwig Mies van der Rohe", "2645"],
  ...
];
Enter fullscreen mode Exit fullscreen mode

Head over to http://localhost:8080/ in your browser. Your app should now look like this:

Vue-Cube-Table

How to Add Apache ECharts to the Vue.js App

In this section, you’ll learn how to install and create different types of charts using Apache ECharts, an open source JavaScript visualization library. You’ll create a pie chart and a bar chart using the data returned from the Cube REST API.

Run the following commands in the Vue project directory to install the echarts and vue-echarts packages:

npm install echarts vue-echarts
Enter fullscreen mode Exit fullscreen mode

The first chart you’ll create is the pie chart, which shows the different classifications of artworks. Go to http://localhost:4000/#/build?query={}, create the fourth query below, and copy its JSON format from the JSON Query tab.

Query4

Then create a new query named paintingsByClassificationQuery in the App.vue file using the query copied from the last step:

paintingsByClassificationQuery:
{
  "measures": ["Artworks.count"],
  "timeDimensions": [],
  "order": {
    "Artworks.count": "desc"
  },
  "dimensions": ["Artworks.classification"],
  "filters": [
    {
    "member": "Artworks.count",
    "operator": "gte",
    "values": ["1000"]
    }
  ]
},
Enter fullscreen mode Exit fullscreen mode

Create a new file named PieChart under the components directory by running the following command:

touch src/components/PieChart.vue
Enter fullscreen mode Exit fullscreen mode

In PieChart.vue, start by adding the template section which simply holds the v-chart element:

<template>
  <div class="charts-wrapper">
    <v-chart class="chart" :option="option" />
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

In the script section, import all the components and methods needed to create the chart from the echarts and vue-echarts packages.

import { use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { PieChart } from "echarts/charts";
import {
  GridComponent,
  TooltipComponent,
  LegendComponent,
  TitleComponent,
} from "echarts/components";
import VChart, { THEME_KEY } from "vue-echarts";
Enter fullscreen mode Exit fullscreen mode

Now you need to use the use method to register the components needed and imported.

use([
  CanvasRenderer,
  PieChart,
  GridComponent,
  TitleComponent,
  TooltipComponent,
  LegendComponent,
]);
Enter fullscreen mode Exit fullscreen mode

Add the exports:

export default {
  name: "PieChart",
  components: {
    VChart,
  },
  props: {
    title: String,
    resultSet: Object,
  },
  provide: {
    [THEME_KEY]: "dark",
  },
}
Enter fullscreen mode Exit fullscreen mode

Now use the Vue.js setup function and pass the props (i.e., the resultSet object).

Below, you’ll parse the resultSet object to return all the names of the classification in the headers array. The PieCharts expects the data to be in an object with keys name and value (for example: {name: Photograph, value: 31367}). Then, you parse the resultSet to store the data in this format in the data array. Add this code inside the export:

setup(props) {
    const headers = props.resultSet
    .rawData()
    .map((item) => Object.values(item)[0]);

    const data = props.resultSet.rawData().map((item) => {
        const currentItem = Object.values(item);
        return { name: currentItem[0], value: currentItem[1] };
    });
}
Enter fullscreen mode Exit fullscreen mode

Now you need to create the option, which contains the configuration of the chart, using Vue.js ref api. Still in the setup function, add the following after the data declaration:

const option = ref({
  title: {
    text: "Paintings Classification",
    left: "center",
  },
  tooltip: {
    trigger: "item",
    formatter: "{a} <br/>{b} : {c} ({d}%)",
  },
  legend: {
    orient: "vertical",
    left: "left",
    data: headers,
  },
  series: [
    {
    name: "Paintings Classification",
    type: "pie",
    radius: "55%",
    center: ["50%", "60%"],
    data: data,
    emphasis: {
        itemStyle: {
        shadowBlur: 10,
        shadowOffsetX: 0,
        shadowColor: "rgba(0, 0, 0, 0.5)",
        },
    },
    },
  ],
});
Enter fullscreen mode Exit fullscreen mode

Inside the option, you define the chart’s name, legend, and tooltip. The data array created in the last step is passed to the data property in the series. Inside the series property, you can set the type of the chart, the radius of the pie chart, the name of the chart, etc.

Finally, return the option object:

return { option };
Enter fullscreen mode Exit fullscreen mode

Here's the complete script section for your reference:

<script>
export default {
  name: "PieChart",
  components: {
    VChart,
  },
  props: {
    title: String,
    resultSet: Object,
  },
  provide: {
    [THEME_KEY]: "dark",
  },
  setup(props) {
    const headers = props.resultSet
    .rawData()
    .map((item) => Object.values(item)[0]);

    const data = props.resultSet.rawData().map((item) => {
        const currentItem = Object.values(item);
        return { name: currentItem[0], value: currentItem[1] };
    });

    const option = ref({
    title: {
        text: "Paintings Classification",
        left: "center",
    },
    tooltip: {
        trigger: "item",
        formatter: "{a} <br/>{b} : {c} ({d}%)",
    },
    legend: {
        orient: "vertical",
        left: "left",
        data: headers,
    },
    series: [
        {
        name: "Paintings Classification",
        type: "pie",
        radius: "55%",
        center: ["50%", "60%"],
        data: data,
        emphasis: {
            itemStyle: {
            shadowBlur: 10,
            shadowOffsetX: 0,
            shadowColor: "rgba(0, 0, 0, 0.5)",
            },
        },
        },
    ],
    });


  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

Add some CSS for styling:

<style scoped>
.chart {
  height: 400px;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Update the App.vue file to import this PieChart.vue file. Import the PieChart component and add it to the components declaration:

...
import PieChart from "./components/PieChart";
...
export default {
  name: "App",
  components: {
    QueryBuilder,
    Card,
    OrderCard,
    Table,
    PieChart,
  },
  ...
}
Enter fullscreen mode Exit fullscreen mode

Add a new section under template in the App.vue file:

<div class="charts-section">
  <query-builder
    :cubejs-api="cubejsApi"
    :query="paintingsByClassificationQuery"
  >
    <template v-slot="{ loading, resultSet }">
    <div v-if="loading" class="loading">Loading...</div>
    <div v-if="!loading && resultSet !== undefined">
        <PieChart
        v-if="!loading"
        title="Classification of Paintings"
        :resultSet="resultSet"
        />
    </div>
    </template>
  </query-builder>
</div>
Enter fullscreen mode Exit fullscreen mode

Then head over to http://localhost:8080/ in the browser. Your chart should now look like this:

Vue-Cube-PieChart

Next, you need to create a bar chart using Apache ECharts. To do this, run the following command to create a BarChart.vue file under the components directory.

touch src/components/BarChart.vue
Enter fullscreen mode Exit fullscreen mode

Add the following code to BarChart.vue:

<template>
  <div class="charts-wrapper">
    <v-chart class="chart" :option="option" />
  </div>
</template>

<script>
import { use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { BarChart } from "echarts/charts";
import {
  GridComponent,
  TooltipComponent,
  LegendComponent,
  TitleComponent,
} from "echarts/components";
import VChart, { THEME_KEY } from "vue-echarts";
import { ref } from "vue";

use([
  CanvasRenderer,
  BarChart,
  GridComponent,
  TitleComponent,
  TooltipComponent,
  LegendComponent,
]);

export default {
  name: "PieChart",
  components: {
    VChart,
  },
  props: {
    title: String,
    resultSet: Object,
  },
  provide: {
    [THEME_KEY]: "light",
  },
  setup(props) {
    const headers = props.resultSet
    .rawData()
    .map((item) => Object.values(item)[0]);

    const data = props.resultSet.rawData().map((item) => {
    const currentItem = Object.values(item);
    return currentItem[1];
    });

    const option = ref({
    title: {
        text: "Paintings by Nationality",
        left: "center",
    },
    tooltip: {
        trigger: "item",
        formatter: "{a} <br/>{b} : {c} ({d}%)",
    },
    xAxis: {
        type: "category",
        data: headers,
    },
    yAxis: {
        type: "value",
    },
    series: [
        {
        name: "Paintings by Nationality",
        type: "bar",
        showBackground: true,
        backgroundStyle: {
            color: "rgba(180, 180, 180, 0.2)",
        },
        data: data,
        emphasis: {
            itemStyle: {
            shadowBlur: 10,
            shadowOffsetX: 0,
            shadowColor: "rgba(0, 0, 0, 0.5)",
            },
        },
        },
    ],
    });

    return { option };
  },
};
</script>
<style scoped>
.chart {
  height: 400px;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Like PieChart, here you import the necessary components and then parse the resultSet object to get data in the required format. In this case, BarChart has an array of values and passes it to the vue-chart component via the option prop.

Create a new query named paintingsByNationalityQuery in your App.vue file:

paintingsByNationalityQuery:
{
  "measures": ["Artworks.count"],
  "dimensions": ["Artworks.nationality"],
  "timeDimensions": [],
  "order": {
    "Artworks.dateacquired": "asc"
  },
  "filters": [
    {
    "member": "Artworks.count",
    "operator": "gte",
    "values": ["1000"]
    }
  ]
},
Enter fullscreen mode Exit fullscreen mode

And then import the BarChart.vue file in App.vue:

<script>
...
import BarChart from "./components/BarChart";
...
export default {
  name: "App",
  components: {
    QueryBuilder,
    Card,
    OrderCard,
    Table,
    PieChart,
    BarChart
  },
...
}
Enter fullscreen mode Exit fullscreen mode

Add this BarChart under the charts-section div like this:

<query-builder :cubejs-api="cubejsApi" :query="paintingsByNationalityQuery">
  <template v-slot="{ loading, resultSet }">
    <div v-if="loading" class="loading">Loading...</div>
    <div v-if="!loading && resultSet !== undefined">
    <BarChart
        v-if="!loading"
        title="Paintings by Nationality"
        :resultSet="resultSet"
    />
    </div>
  </template>
</query-builder>
Enter fullscreen mode Exit fullscreen mode

After adding BarChart, your chart will look like this:

Bar Chart

And your final app will look like this:

Final App

Conclusion

In this tutorial, you learned how you can use Cube, an open source analytical API platform, to create components for a real-time dashboard using Vue.js. You also learned how to create charts for this dashboard using Apache ECharts.

For additional information to help get you started, take a look at the official Cube docs.

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 Vue.js in your next production gig or your next pet project.

💖 💪 🙅 🚩
adnanrahic
Adnan Rahić

Posted on February 10, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related