Cover image for "Creating a Data Visualization Dashboard with Vue" blog post

Creating custom dashboards is hard work! Whether it be an analytics dashboard to monitor your site’s traffic and social presence, or a SaaS dashboard to see how your product is growing, creating a dashboard from scratch can be quite complex and time-consuming. This is the second article in our series about integrating ZingChart with Vue to create an interactive dashboard. If you'd like to learn more about the zingchart-vue component, check out the first article in this series.

This interactive Vue sales dashboard includes ZingChart’s bar, line, and pie chart types, as well as a ZingGrid data table.

With the use of third party libraries, we can alleviate most of the heavy lifting and create dashboards that have complex interactivity with minimal effort. This article will be an overview of the  libraries needed to create almost any type of dashboard.

  • Vue.js— We will be using the popular Vue JavaScript framework to build our dashboard around. With its robust data handling and rich developer community with UI widgets, this is the perfect framework to set the groundwork.
  • ZingChart — To create dashboards with unique visualizations, we will need a library that covers all use cases. ZingChart provides over 30 different chart types, including the basics such as Line, Bar, and Pie Charts, but also more unique visualizations such as Word Cloud, Gauge, and Radar. The library even has Maps!
  • ZingGrid — Dashboards aren’t complete without a data table to display details about our data in a logical format. A complementary product to ZingChart, ZingGrid will provide a useful set of features to handle our data including filtering, sorting, and searching. With its unique compatibility with ZingChart allowing rich interactivity between the two products, it’s a no-brainer to add this library to our arsenal.

Visualizing Your Data with ZingChart

We can easily customize and add charts to our dashboard using the zingchart-vue component. The component allows for easy data-binding, and exposes out ZingChart’s events and methods to the component level. For more information about the ZingChart Vue component, check out the previous article in this series:

Go to the first blog post in this series.

Inspecting Your Data with ZingGrid

Charts and data visualizations are great for understanding your data, but when you need to see specific details of an event, it can be tedious. Data grids and tables are useful for organizing large amounts of data and for finding specific entries in that data.

Adding Data to the Grid

Unlike ZingChart, ZingGrid is built as a web component and is portable in any framework, including Vue. We can easily add data to our grid with the data attribute and the .prop modifier, which stringifies the data before being sent to ZingGrid. This not only allows ZingGrid to retain a fresh copy of the data, but automatically converts it to a format that web components can understand.

<zing-grid :data.prop="myData"></zing-grid>

Filtering, Sorting, and Pagination, Oh My!

A list of data entries isn’t useful unless there is a way to navigate through the list easily. ZingGrid provides multiple useful features that let you easily sort through your data. Best of all, adding these features is as simple as adding an attribute to the zing-grid element.

<zing-grid :data.prop="myData" filter sort pager></zing-grid>
This interactive ZingGrid data table includes filtering, sorting, and pagination.

Interactivity with Other Components

Like ZingChart, ZingGrid allows for deep integration to its environment with API methods and events. You can learn more about these in the docs: https://www.zinggrid.com/docs/api-methods

Now that we have a basic understanding of the tools we’re going to utilize, let’s build our dashboard!


Getting Started

For this tutorial, you will need the vue-cli installed on your computer. This will allow us to quickly scaffold, run, and test our dashboard with ease. If you don’t have this cli tool installed, head over to https://cli.vuejs.org/#getting-started to start there.

Once it’s installed, we’ll create a project with our dashboard starter code.

vue create --preset zingsoftinc/vue-dashboard#starter myDashboard

After this tutorial, you should be familiar with:

  • Basic data flow and manipulation in a dashboard
  • Creating ZingChart and ZingGrid in Vue
  • Communicating across multiple widgets in the dashboard

The starter code contains styling and structure already defined to help you focus on the important highlights of creating your dashboard.

Project Overview

myDashboard/
├── src/
│   ├── App.vue
│   ├── main.js
│   │── data/
│   │  └── transactions.js
│   ├── components/
│   │  ├── LatestTransactionsChart.vue
│   │  ├── TransactionDetailsGrid.vue
└───└──└── TransactionBreakdownChart.vue
  • App.vue — The root Vue component which contains our dashboard
  • main.js — The entry point for our Vue application
  • components/ — Various charts and grids that we will learn how to connect together
  • data/ — The dataset that our dashboard will build off of. In future tutorials, we’ll learn how to connect and filter data to remote sources, including databases.

The starter code has three components pre-defined and styled for you. Our focus will be on how to manipulate our dataset to pass data to each component, and to tie-in interactions across those components.

Our Dataset

For this tutorial, we’ll be creating a sales analytics dashboard based on a set of transactions. Our dataset is simply an array of these objects:

{
  "id":1,
  "first_name":"Ketti",
  "last_name":"Latham",
  "email":"klatham0@time.com",
  "company":"InnoZ",
  "amount":"1529.29",
  "timestamp":1554550724000,
  "license_type":"saas",
  "purchase_type":"new"
}

Important attributes :

  • license_type — Either SaaS, internal, website, or enterprise
  • purchase_type — Either new, renewal, or cancellation (if the user has opted out from a renewal)

Our Components

We will be focusing on three different components in this tutorial, comprised of ZingChart and ZingGrid.

A close-up of some of the components in our Vue dashboard
  • LatestTransactionsChart.vue — A line chart comparing the amount earned in dollars over time
  • TransactionBreakdownChart.vue — A pie chart breaking down the occurrence of each transaction type
  • TransactionDetailsGrid.vue — A grid listing each transaction during the period

Step 1: Passing Data to the Components

For our components to render any charts or grids, we need to pass the data to each component individually since specific charts and grids may need to have the data formatted differently.

First, we’ll import our data from the transactions file into our App.vue, along with our chart components. We also register the components in our Vue app to be used.

// App.vue
<script>
  import transactions from './data/transactions.js';
  import LatestTransactionsChart from "./components/LatestTransactionsChart.vue";
  import TransactionBreakdownChart from "./components/TransactionBreakdownChart.vue";
  import TransactionDetailsGrid from "./components/TransactionDetailsGrid.vue";
export default {
    name: 'app',
    // Register the components to be used
    components: {
      LatestTransactionsChart,
      TransactionBreakdownChart,
      TransactionDetailsGrid,
    },
    data() {
      return {
        transactions,
      }
    }
  }
</script>

Then we’ll pass the transactions object into each of our three components.

// App.vue
<template>
  <div id="app">
    <section class="dashboard">
      ...
      <div class="dashboard__row">
        <latest-transactions-chart ref="latestTransactions" :entries="transactions"/>
        <transaction-breakdown-chart ref="transactionBreakdown" :entries="transactions"/>
      </div>
      <div class="dashboard__row">
        <transaction-details-grid :entries="transactions"/>
      </div>
    </section>
  </div>
</template>

Let’s dive into each of our three components to manipulate the data to render out a chart or a grid.

Latest Transactions Chart

For the “Latest Transactions Chart”, we need to format our transactions array into a single dimensional array containing a pair of values: the timestamp, followed by the dollar amount for each transaction.

// LatestTransactionsChart
<template>
  <zingchart :data="chartConfig"></zingchart>
</template>
<script>
  export default {
    props: ['entries'],
    computed: {
     values() {
        return this.entries.map(o => {
          return [o.timestamp, parseFloat(o.amount.slice(1,-1))]
       });
     },
     chartConfig() {
       ...
       series: [{
         values: this.values,
       }],
       ...
     },
    },
  }
</script>

We write both the values and chartConfig as computed properties rather than data so we can automatically track any changes that occur to the data from the parent App.vue component.

Transaction Breakdown Chart

Similarly for our “Transaction Breakdown” Chart, we need to format the data as an array of objects, with each value containing the total value of the transaction types. ZingChart will calculate the overall percentage for us, but we’ll need to do a bit of summations over the transactions:

// TransactionBreakdown
<template>
  <zingchart :data="chartConfig"></zingchart>
</template>
<script>
  export default {
    props: ['entries'],
    computed: {
     values() {
      const categories = this.entries.reduce((acc, transaction) => {
        acc[transaction.purchase_type] = acc[transaction.purchase_type] || 0;
        acc[transaction.purchase_type]++;
        return acc;
      }, {});
      return Object.keys(categories).map((name) => {
        return {
          values: [categories[name]],
          text: name
        }
      })
     },
     chartConfig() {
const colors = [
        {
          backgroundColor: '#04A3F5',
          hoverState: {
            backgroundColor: '#45D6C4'
          }
        },
        {
          backgroundColor: '#98D1EE',
          hoverState: {
            backgroundColor: '#45D6C4'
          }
        },
        {
          backgroundColor: '#295A73',
          hoverState: {
            backgroundColor: '#45D6C4'
          }
        },
      ]; 
      
       ...
        series: this.values.map((o,index) => Object.assign(o, colors[index])),
       ...
},
    },
  }
</script>

Transaction Details Grid

As for the “Transaction Details” Grid, ZingGrid natively accepts an array of objects so all we have to do is pass the data!

<transaction-details-grid :entries="transactions"></transaction-details-grid>

Step 2: Adding an External Component to Control Our Data

If all went smoothly, you should see the charts and grids populated with a full year’s worth of transactions. But what if we only wanted to view a month’s view of transactions? We would need to control our data at the App level to do so.

We will be adding an additional library to help us select dates via a calendar component:

V-calendar to filter our transactions by date.
Go to VCalendar.io to learn more about this date picker

First let’s install the library to our project:

$ npm i v-calendar

And add the following lines to our main.js, just before the new Vue constructor is invoked.

import VCalendar from 'v-calendar';
Vue.use(VCalendar);

Inside of our App.vue file we’ll need to add a new field in our data object to keep track of the start and end dates that the calendar is displaying. We will default to showing the current month’s transactions.

data() {
    return {
      transactions,
      range: {
        start: new Date().setDate(1), 
        end: new Date() 
      }
    };
  }

We will be using the range property to control both the start and end dates with one widget. Next, we’ll add the v-calendar component to our template, binding both the start and end dates to our new data fields.

<header>
  <h4>Date Range</h4>
  <v-date-picker mode="range" v-model="range"/>
</header>

A new computed property called filteredTransactions will be needed to return a subset of the transactions, based on the date range.

computed: {
    filteredTransactions() {
      return this.transactions.filter(entry => {
        return (
          entry.timestamp >= this.range.start.getTime() &&
          entry.timestamp < this.range.end.getTime()
        );
      });
    },
...

Finally, we’ll update all the components to accept the new filtered date range:

<template>
  <div>
    <latest-transactions-chart :entries="filteredTransactions"></latest-transactions-chart>
    <transaction-breakdown-chart :entries="filteredTransactions"></transaction-breakdown-chart>
    <transaction-details-grid :entries="filteredTransactions"></transaction-details-grid>
  </div>
</template>

The charts and grids should now show a subset range of the transactions! This also demonstrates how data modeling works in Vue: one-way reactivity from the parent to its children.

Step 3: Adding Interactions Between the Components

Our dashboard is looking more complete, but to take it a step further we can add shared interactivity between the components. Both ZingChart and ZingGrid come with rich API Methods and Events, from reacting to MouseEvents to modifying state from external methods.

We will be demonstrating how to control mouseover events from ZingGrid to highlight the same entry in both our Latest Transactions and Transaction Breakdown Charts.

To begin, we need to listen for ZingGrid’s row mouseover event. ZingGrid calls this a record:mouseover event. We perform this in the component’s mounted() lifecycle callback once the grid has finished rendering.

// TransactionDetailsGrid.vue
mounted() {
    // Attach an event listener to ZingGrid
    this.$refs.myGrid.addEventListener('record:mouseover', (e) => {
      this.$emit('mouseover', e);
    });
  }

Since we have encapsulated each of our chart and grid components in their own respective components for code clarity, we need to emit this event back up into our App.vue parent component.

Back in our App.vue file, we need to delegate the mouseover event from ZingGrid to our children charts.

First, we define a callback that the mouseover event should listen to.

// App.vue
<transaction-details-grid @mouseover="handleEntryHover"></transaction-details-grid>

In our new method handleEntryHover, we reference both our charts and call on the ZingChart methods that are available thanks to the zingchart-vue component.

methods: {
  handleEntryHover(e) {
    // Obtain references to the charts
    let latestTransactions = this.$refs.latestTransactions;  
    let transactionBreakdown = this.$refs.transactionBreakdown;
  }
}

Since ZingGrid emits CustomEvents, the information that we need will be under e.detail. Inside, we see a rich amount of information about the grid and the event.

For our Latest Transactions chart, we want a guide to appear on the chart when the user hovers over the corresponding row in our grid. To do so, we need to call the chart’s setguide method and pass it an object containing the specific keyvalue (x-axis value), or in this case, the timestamp.

// Set our guide corresponding to the correct timestamp
latestTransaction.setguide({
  keyvalue: e.detail.ZGData.data.timestamp
});

For our Transaction Breakdown chart, we will need to determine the row entry’s purchase_type and the corresponding index that the data is defined when we created the chart.

We utilize the getseriesdata method of the chart to obtain the data used to create the chart to find the index. Once found, we then call the showhoverstate to change the pie slice color to highlight the entry in focus. Recall that the data format of the pie chart is an array of a single entry. The showhoverstate method requires a nodeindex so we default at 0.

const indexInFocus = transactionBreakdown
  .getseriesdata()
  .findIndex(o => o.text === e.detail.ZGData.data.purchase_type);
  
transactionBreakdown.showhoverstate({
  plotindex: indexInFocus,
  nodeindex: 0
});

All together:

handleEntryHover(e) {
  // Obtain references to the charts
  let latestTransactions = this.$refs.latestTransactions;
  let transactionBreakdown = this.$refs.transactionBreakdown;
// Set our guide corresponding to the correct timestamp
  latestTransaction.setguide({
    keyvalue: e.detail.ZGData.data.timestamp,
  });
// Highlight the pie slice in focus
  const indexInFocus = transactionBreakdown
    .getseriesdata()
    .findIndex(o => o.text === e.detail.ZGData.data.purchase_type);
  transactionBreakdown.showhoverstate({
    plotindex: indexInFocus,
    nodeindex: 0
  });
}

And there you have it! Interactions across ZingGrid and ZingChart.

Interactions with multiple charts and a grid in our sales dashboard.

Check out a completed, working example of this Vue dashboard in the master branch of our starter code at https://github.com/zingsoftinc/vue-dashboard and at https://glitch.com/~vue-dashboard-starter


We took our example a step further and created a sales dashboard with custom sparklines and more interactions and features. You can check out a working example and the code for it here: https://glitch.com/~vue-dashboard

This advanced version of the Vue sales dashboard featured in this tutorial includes even more interactivity.

Next time, we’ll show you how to connect a live database, to search, filter, and query directly from your dashboard.

To be notified of future articles in this series, follow us on Twitter!


ZingChart logo

A pioneer in the world of data visualization, ZingChart is a powerful JavaScript library built with big data in mind. With over 35 chart types and easy integration with your development stack, ZingChart allows you to create interactive and responsive charts with ease.

comments powered by Disqus