Cube.js Blog

Chart.js Example with Dynamic Dataset

Author avatarArtyom KeydunovSeptember 25, 2019Tutorials
Chart.js Example with Dynamic Dataset
Show Original

I've already covered building a static dashboard with Cube.js and Chart.js in this tutorial. Now, I’m going to show you how to dynamically change the underlying chart’s data based on the user’s input.

We’ll let the user pick a date range and based on that, reload the chart. When a user picks a new set of dates, a new request will be sent to the Cube.js API. The Cube.js server will generate new SQL code, execute it against the database, and send the result back to the client. And finally, the client re-renders a chart with the new data.

Here is a CodeSandbox demo of what we are going to build. You can click "Open in Editor" to check the source code.

Setting up an API

We're going to create a new Cube.js app and connect it to a cloud-based Postgres dataset.

First, make sure you have Docker installed on your machine. You can also get started using Node.js.

Second, create new a folder for your Cube.js app and navigate to it:

mkdir chartjs-dynamic-data
cd chartjs-dynamic-data
mkdir schema

Then, create a new docker-compose.yml file with Cube.js configuration. We'll use environment variables for configuration and instruct Cube.js to connect to a publicly available cloud-based Postgres dataset:

cat > docker-compose.yml << EOL
version: '2.2'
services:
cube:
image: cubejs/cube:latest
ports:
- 4000:4000 # Cube.js API and Developer Playground
- 3000:3000 # Dashboard app, if created
environment:
- CUBEJS_DB_TYPE=postgres
- CUBEJS_DB_HOST=demo-db.cube.dev
- CUBEJS_DB_USER=cube
- CUBEJS_DB_PASS=12345
- CUBEJS_DB_NAME=ecom
- CUBEJS_API_SECRET=SECRET
- CUBEJS_DEV_MODE=true
volumes:
- .:/cube/conf
EOL

The last step is to run Cube.js:

docker compose up

Now you can open localhost:4000 in your browser. You will see Developer Playground, a companion tool that will help you develop your Cube.js app.

We're going to use it to generate the data schema for the database. Please navigate to the Schema tab, select the public schema, and click "Generate Schema". Great! Now you can navigate to the Build tab to play around with the data:

Developer Playground

There are multiple ways to deploy Cube.js apps. In this tutorial, we are going to use the Cube.js API deployed to Cube Cloud at https://ecom.cubecloudapp.dev/cubejs-api/v1.

Simple Chart

We'll use CodeSandbox, an online editor for rapid web development, to build the front-end app. You can check out the final source code and the demo app. Feel free to fork it and play around.

We are going to use the Vanilla template from CodeSandbox. To keep things simple, we'll not add any framework, such as React or Vue.

CodeSandbox

The first step is to include the Cube.js client and Chart.js libraries. Insert the following code inside the dependencies key of the package.json file. It should look like this:

// ...
"dependencies": {
"@cubejs-client/core": "0.27.15",
"chart.js": "3.2.1",
"moment": "2.29.1",
"parcel-bundler": "^1.6.1"
},
// ...

To initialize the Cube.js client, we need to pass an API URL along with the secret. We'll also import the libraries we've just added. Add these lines to the index.js file:

import cubejs from "@cubejs-client/core";
import Chart from "chart.js/auto";
import moment from "moment";
const cubejsApi = cubejs(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1OTQ2NjY4OTR9.0fdi5cuDZ2t3OSrPOMoc3B1_pwhnWj4ZmM3FHEX7Aus",
{ apiUrl: "https://ecom.cubecloudapp.dev/cubejs-api/v1" }
);

Once the client is initialized, we can request data from the API and visualize it. The load function accepts a query, which is a plain JavaScript object, and returns a promise. You can learn more about the query format in the docs.

cubejsApi
.load({
measures: ["Orders.count"],
timeDimensions: [ {
dimension: "Orders.createdAt",
granularity: `day`,
dateRange: [`08/01/2020`,`09/01/2020`]
} ]
})
.then(resultSet => {
new Chart(document.getElementById("chart"), {
type: "line",
options: {},
data: chartJsData(resultSet)
});
});

We are loading Orders.count, which is grouped by the created day to plot as a line chart. To make this code work, we need to make a couple of things. First, add the <canvas> tag to the inside the <body> in the index.html file:

<canvas id="chart"></canvas>

Next, we need to define the chartJsData function, which should accept a resultSet returned from Cube.js and format it for Chart.js. Add this to the index.js file:

var chartJsData = function (resultSet) {
return {
datasets: [
{
label: "Orders Count",
data: resultSet.chartPivot().map(function (r) {
return r["Orders.count"];
}),
backgroundColor: "rgb(255, 99, 132)"
}
],
labels: resultSet.categories().map(function (c) {
return moment(c.x).format("DD MMM");
})
};
};

That is all we need to load the data and visualize it as a static line chart:

Static Chart

Dynamic Data

Next, we’re going to add a date range picker and load data dynamically based on the date range selected by the user.

Let's add the flatpickr dependency we'll be using as a lightweight date picker in the package.json file. It should look like this:

// ...
"dependencies": {
"@cubejs-client/core": "0.27.15",
"chart.js": "3.2.1",
"moment": "2.29.1",
"flatpickr": "4.6.9",
"parcel-bundler": "^1.6.1"
},
// ...

Next, let's import this library in the index.js file:

import flatpickr from "flatpickr";

Next, let’s wrap the code to render a chart into the drawChart function, which is going to accept two arguments: start date and end date.

let chart;
var drawChart = function (startDate, endDate) {
cubejsApi
.load({
measures: ["Orders.count"],
timeDimensions: [
{
dimension: "Orders.createdAt",
granularity: `day`,
dateRange: [startDate, endDate]
}
]
})
.then((resultSet) => {
if (chart) {
chart.data = chartJsData(resultSet);
chart.update();
} else {
chart = new Chart(document.getElementById("chart"), {
type: "line",
options: {},
data: chartJsData(resultSet)
});
}
});
};

Besides making the dateRange dynamic, we’re also saving the current chart into the global variable chart so we can update it later when we need to re-render the chart.

Finally, we can add an input to the index.html file and make it a date range picker. Let's add a CSS import inside the head and an input inside the body:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
/// ...
<input id="dates" style="font-size: 20pt;" />

In the index.js file, after the drawChart function:

const MIN_DATE = "2020-08-01";
const MAX_DATE = "2020-09-01";
flatpickr("#dates", {
mode: "range",
dateFormat: "Y-m-d",
defaultDate: [MIN_DATE, MAX_DATE],
onChange: function (selectedDates) {
if (selectedDates.length === 2) {
drawChart(selectedDates[0], selectedDates[1]);
}
}
});
drawChart(MIN_DATE, MAX_DATE);

That is it! Now we have a fully working dynamic and interactive chart.

Dynamic Chart

And here's an interactive CodeSandbox that we've built. You can select different dates from the date picker and see how the chart is changing.

If you have any questions about this tutorial or about Cube.js in general, feel free to ask them in Slack.

Cube.js Digest

Subscribe for the Cube.js news, releases, and latest posts.