🎉 New release for Pusher Chatkit - Webhooks! Extend your in-app chat functionality
Hide
Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

Build a live graph with D3.js

  • Ayooluwa Isaiah
August 24th, 2018
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

    <!DOCTYPE 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:

    // server.js

    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:

    // app.js

    // set the dimensions and margins of the graph
    const margin = { top: 20, right: 20, bottom: 30, left: 40 };
    const width = 960 - margin.left - margin.right;
    const height = 500 - margin.top - margin.bottom;

    // set the ranges for the graph
    const x = d3
      .scaleBand()
      .range([0, width])
      .padding(0.1);

    const y = d3.scaleLinear().range([height, 0]);

    // append the container for the graph to the page
    const container = d3
      .select('body')
      .append('div')
      .attr('class', 'container');

    container.append('h1').text('Who will win the 2018/19 Premier League Season?');

    // append the svg object to the body of the page
    // append a 'group' element to 'svg'
    // moves the 'group' element to the top left margin
    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 + ')');

    // Create a skeleton structure for a tooltip and append it to the page
    const tip = d3
      .select('body')
      .append('div')
      .attr('class', 'tooltip');

    // Get the poll data from the `/poll` endpoint
    fetch('http://localhost:4000/poll')
      .then(response => response.json())
      .then(poll => {
        // add the x Axis
        svg
          .append('g')
          .attr('transform', 'translate(0,' + height + ')')
          .attr('class', 'x-axis')
          .call(d3.axisBottom(x));

        // add the y Axis
        svg
          .append('g')
          .attr('class', 'y-axis')
          .call(d3.axisLeft(y));

        update(poll);
      });

    function update(poll) {
      // Scale the range of the data in the x axis
      x.domain(
        poll.map(d => {
          return d.name;
        })
      );

      // Scale the range of the data in the y axis
      y.domain([
        0,
        d3.max(poll, d => {
          return d.votes + 200;
        }),
      ]);

      // Select all bars on the graph, take them out, and exit the previous data set.
      // Enter the new data and append the rectangles for each object in the poll array
      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'));

      // update the x-axis
      svg.select('.x-axis').call(d3.axisBottom(x));

      // update the y-axis
      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:

Update the graph in realtime with Pusher

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.

    // app.js

    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.

Trigger updates from the server

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.

Clone the project repository
  • Data Visualization
  • JavaScript
  • Node.js
  • Channels

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.