You will need Node and npm installed on your machine.

Visual representations of data are one of the most effective means of conveying complex information and D3.js provides great tools and flexibility to create these data visualizations.

D3.js is a JavaScript library used for producing dynamic, interactive data visualizations in web browsers using SVG, HTML and CSS.

In this tutorial, we'll explore how to build a realtime graph with D3.js and Pusher Channels. If you want to play around with the code as you read this tutorial, check out this GitHub repository, which contains the final version of the code.

Prerequisites

To complete this tutorial, you need to have Node.js and npm installed. The versions I used while creating this tutorial are as follows:

Node.js v10.4.1

npm v6.3.0

You also need to have http-server installed on your machine. It can be installed through npm by running the following command: npm install http-server .

Although no Pusher knowledge is required, a basic familiarity with JavaScript and D3.js will be helpful.

Getting started

To get started, create a new directory for the app we will build. Call it realtime-graph or whatever you like. Inside the newly created directory, create a new index.html file and paste in the following code:

//index.html < hml lang = "en" > < head > < meta charset = "UTF-8" > < meta name = "viewport" content = "width=device-width, initial-scale=1.0" > < meta http-equiv = "X-UA-Compatible" content = "ie=edge" > < link rel = "stylesheet" href = "style.css" > < title > Realtime D3 Chart </ title > </ head > < body > < script src = "https://js.pusher.com/4.2/pusher.min.js" > </ script > < script src = "https://d3js.org/d3.v5.min.js" > </ script > < script src = "app.js" > </ script > </ body > </ html >

As you can see, the HTML file is just pulling up the styles and scripts we need to build the graph. We're making use of D3.js to build the chart and Pusher to add realtime functionality. The app.js file is where the code for the frontend of the app will be written.

Before we start implementing the chart, let's add the styles for the app in style.css :

// style.css html { height: 100%; box-sizing: border-box; padding: 0; margin: 0; } *, *::before, *::after { box-sizing: inherit; } body { height: 100%; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; overflow: hidden; background: linear-gradient(135deg, #ffffff 0%,#e8f1f5 100%); } .container { position: absolute; padding: 20px; top: 50%; left: 50%; background-color: white; border-radius: 4px; transform: translate(-50%, -50%); box-shadow: 0px 50px 100px 0px rgba(0,0,102,0.1); text-align: center; } .container h1 { color: #333; } .bar { fill: #6875ff; border-radius: 2px; } .bar:hover { fill: #1edede; } .tooltip { opacity: 0; background-color: rgb(170, 204, 247); padding: 5px; border-radius: 4px; transition: opacity 0.2s ease; }

Install the server dependencies

Assuming you have Node and npm installed, run the following command to install all the dependencies we will need for the server component of the application:

npm install express dotenv cors pusher

Pusher setup

Head over to the Pusher website and sign up for a free account. Select Channels apps on the sidebar, and hit Create Channels app to create a new app.

Once your app is created, retrieve your credentials from the API Keys tab, then add the following to a new variables.env file in the root of your project directory.

// variables.env PUSHER_APP_ID=<your app id> PUSHER_APP_KEY=<your app key> PUSHER_APP_SECRET=<your app secret> PUSHER_APP_CLUSTER=<your app cluster>

Set up the server

Now that we've installed the relevant dependencies and our Pusher account has been setup, we can start building the server.

Create a new file called server.js in the root of your project directory and paste in the following code:

require ( 'dotenv' ).config({ path : 'variables.env' }); const express = require ( 'express' ); const cors = require ( 'cors' ); const poll = [ { name : 'Chelsea' , votes : 100 , }, { name : 'Arsenal' , votes : 70 , }, { name : 'Liverpool' , votes : 250 , }, { name : 'Manchester City' , votes : 689 , }, { name : 'Manchester United' , votes : 150 , }, ]; const app = express(); app.use(cors()); app.get( '/poll' , (req, res) => { res.json(poll); }); app.set( 'port' , process.env.PORT || 4000 ); const server = app.listen(app.get( 'port' ), () => { console .log( `Express running → PORT ${server.address().port} ` ); });

Save the file and run node server.js from the root of your project directory to start the server.

Set up the app frontend

The frontend of the application will be written in the app.js file we referenced earlier. Create this file in the root of your project directory and paste the following code therein:

const margin = { top : 20 , right : 20 , bottom : 30 , left : 40 }; const width = 960 - margin.left - margin.right; const height = 500 - margin.top - margin.bottom; const x = d3 .scaleBand() .range([ 0 , width]) .padding( 0.1 ); const y = d3.scaleLinear().range([height, 0 ]); const container = d3 .select( 'body' ) .append( 'div' ) .attr( 'class' , 'container' ); container.append( 'h1' ).text( 'Who will win the 2018/19 Premier League Season?' ); const svg = container .append( 'svg' ) .attr( 'width' , width + margin.left + margin.right) .attr( 'height' , height + margin.top + margin.bottom) .append( 'g' ) .attr( 'transform' , 'translate(' + margin.left + ',' + margin.top + ')' ); const tip = d3 .select( 'body' ) .append( 'div' ) .attr( 'class' , 'tooltip' ); fetch( 'http://localhost:4000/poll' ) .then( response => response.json()) .then( poll => { svg .append( 'g' ) .attr( 'transform' , 'translate(0,' + height + ')' ) .attr( 'class' , 'x-axis' ) .call(d3.axisBottom(x)); svg .append( 'g' ) .attr( 'class' , 'y-axis' ) .call(d3.axisLeft(y)); update(poll); }); function update ( poll ) { x.domain( poll.map( d => { return d.name; }) ); y.domain([ 0 , d3.max(poll, d => { return d.votes + 200 ; }), ]); svg .selectAll( '.bar' ) .remove() .exit() .data(poll) .enter() .append( 'rect' ) .attr( 'class' , 'bar' ) .attr( 'x' , d => { return x(d.name); }) .attr( 'width' , x.bandwidth()) .attr( 'y' , d => { return y(d.votes); }) .attr( 'height' , d => { return height - y(d.votes); }) .on( 'mousemove' , d => { tip .style( 'position' , 'absolute' ) .style( 'left' , ` ${d3.event.pageX + 10 } px` ) .style( 'top' , ` ${d3.event.pageY + 20 } px` ) .style( 'display' , 'inline-block' ) .style( 'opacity' , '0.9' ) .html( `<div><strong> ${d.name} </strong></div> <span> ${d.votes} votes</span>` ); }) .on( 'mouseout' , () => tip.style( 'display' , 'none' )); svg.select( '.x-axis' ).call(d3.axisBottom(x)); svg.select( '.y-axis' ).call(d3.axisLeft(y)); }

In the code block above, we've created a basic bar chart using the initial data received via the /poll endpoint. If you're familiar with how D3 works, the code should be familiar to you. I've added comments in key parts of the code to walk you through how the chart is constructed.

In a new terminal, start a development server to serve the index.html file:

npx http-server

I'm using http-server here, but you can use whatever server you want. You can even open index.html in the browser directly.

At this point, your graph should look like this:

Let's make sure that updates to the poll can be reflected in the app's frontend in realtime with Pusher Channels. Paste the following code at the end of the app.js file.

const pusher = new Pusher( '<your app key>' , { cluster : '<your app cluster>' , encrypted : true , }); const channel = pusher.subscribe( 'poll-channel' ); channel.bind( 'update-poll' , data => { update(data.poll); });

Here, we opened a connection to Channels and used the subscribe() method from Pusher to subscribe to a new channel called poll-channel . Updates to the poll are listened for via the bind method, and the update() function is invoked with the latest data once an update is received so that the graph is re-rendered.

Don’t forget to replace the <your app key> and <your app cluster> placeholders with the appropriate details from your Pusher account dashboard.

We're going to simulate a poll that updates every second and use Pusher to trigger an update when the data changes so that subscribers to the poll (the client) can receive the updated data in realtime.

Add the following code at the top of server.js below the other imports:

const Pusher = require ( 'pusher' ); const pusher = new Pusher({ appId : process.env.PUSHER_APP_ID, key : process.env.PUSHER_APP_KEY, secret : process.env.PUSHER_APP_SECRET, cluster : process.env.PUSHER_APP_CLUSTER, encrypted : true , }); function getRandomNumber ( min, max ) { return Math .floor( Math .random() * (max - min) + min); } function increment ( ) { const num = getRandomNumber( 0 , poll.length); poll[num].votes += 20 ; } function updatePoll ( ) { setInterval( () => { increment(); pusher.trigger( 'poll-channel' , 'update-poll' , { poll, }); }, 1000 ); }

Then change the /poll endpoint to look like this:

app.get( '/poll' , (req, res) => { res.json(poll); updatePoll(); });

The /poll route sends the initial poll data to the client and calls the updatePoll() function which increments the votes for a random club at three second intervals and triggers an update on the poll-channel which we created on the client in the last step.

Kill your server and restart it by running node server.js from the root of your project directory. At this point, you should have a bar graph that updates in realtime.

Conclusion

You have seen the procedure for creating a bar graph with D3.js and how to it in realtime with Pusher Channels. It was easy enough, wasn't it?

We have covered a simple use case for Pusher and D3 but one that's only scratching the surface. I recommend digging into the docs to find more about Pusher and other awesome features it has.

Thanks for reading! Remember that you can find the complete source code for this tutorial in this GitHub repository.