Real-time Chart in JavaScript with ChartJS and Hamoni Sync

Peter Mbanugo - Apr 13 '18 - - Dev Community

Real-time data is data that is presented as it is acquired. It is often used in tracking or monitoring systems like traffic GPS system, auction/bidding applications and stock trading applications. Charts help with a graphical representation of this data and help the viewer make a decision easily.

In this post, I’ll show you how to make real-time chart in JavaScript. We’ll be building a basic voting web application with a page for voting, and another page with a real-time chart showing the voting result as it happens. Here’s a peek at what we’ll be building

I’ll be using Chart.js and Hamoni Sync to build it. Chart.js is a simple JavaScript charting library. Hamoni Sync is a real-time state synchronisation service which enables you to synchronise your application state in real-time. The vote result is the state we want show users in real-time. Hamoni Sync makes this easier by allowing you to define state for your application, while you avoid designing complex state logic around publish/subscribe systems.

Setup the code

We will be using a project template which already has the HTML pages and node app to serve the static files. Follow the instruction below to set it up

  1. Clone the repo from GitHub by running git clone https://github.com/pmbanugo/realtime-chartjs-hamoni-starter.git in the command line. If you’re not using git, you can download it here.
  2. Switch to the project directory cd realtime-chartjs-hamoni-starter
  3. Install the dependencies by running npm install. This will install express and Hamoni Sync node modules. # Backend

The server.js file contains code to server HTML pages in the public folder. Lines 9 - 14 defines a default state for the voting application. It contains 4 candidates with their vote count as 0.


    let voteData = [
      { candidate: "Peter Mbanugo", vote: 0 },
      { candidate: "Angela Daniels", vote: 0 },
      { candidate: "Rose Philly", vote: 0 },
      { candidate: "James Crump", vote: 0 }
    ];
Enter fullscreen mode Exit fullscreen mode

It also defines a REST endpoint for voting defined from lines 18 to 30. When a vote comes in, we may want to save that data to a database and then update the chart with an up-to-date result of the vote. The chart state will be communicated in real-time using Hamoni Sync. On line 40 I have declared a statement to initialise Hamoni but we need an account ID and app ID.

Create Hamoni Account and App

Login to the Hamoni dashboard (or Signup if you don’t already have an account). When logged in you can click the Show Account ID button to see your account ID. We need a Hamoni App ID, hence we need to create an app from the dashboard. An app is a container that can hold application state. You’d typically want to have a separate Hamoni app for each of your application.

Under the “Create Application” header, enter an application name and click the create button. Within a few seconds, you should see it created and displayed in the application list section.

Hamoni dashboard

Copy the app and account ID and replace them with the string value on line 40 in server.js

Create application state in Hamoni

Now we need to create state in Hamoni Sync. To do that we need to use a sync primitive. Sync primitives are what you use to define and manipulate state. They’re basically a way to categorise or differentiate kinds of data to be stored. There are 3 sync primitives:

  1. Value Primitive: This kind of state holds simple information represented with datatypes like string, boolean or numbers. It is best suited for cases such as unread message count, toggles, etc.
  2. Object Primitive: Object state represents states that can be modelled as a JavaScript object. An example usage could be storing the score of a game.
  3. List Primitive: This holds a list of state objects. A state object is a JavaScript object. You can update an item based on its index in the list.

I’ll be using an object primitive for this post.

Add the following function in server.js

    function createState() {
      hamoni
        .createObject("election", voteData)
        .then(statePrimitive => {
          console.log("election state created");
          state = statePrimitive;
        })
        .catch(console.log);
    }
Enter fullscreen mode Exit fullscreen mode

This creates a state with the name of election and state value using the variable voteData. Then we need to connect to the Hamoni Sync server. We do this by calling hamoni.connect(). Add the following code below the function createState()


    hamoni.connect().then(() => createState()).catch(console.log);
Enter fullscreen mode Exit fullscreen mode

If it connects successfully to the server, we call the createState() function to create the state for our chart.

Update application state

We want to update the state when a new vote comes in. The object primitive which holds our state has an update() method which can be used to update the state. We will update the election state when the user calls /vote endpoint of our API. Below lines 27, add the following code to update the state

    app.post("/vote", function(req, res) {
      ....
      state.update(voteData);
      ....
    });
Enter fullscreen mode Exit fullscreen mode

This will update the state and broadcast the update to connected clients.

Frontend

Our backend is ready to accept votes and update the state. Now we need to build the frontend to send in votes and display realtime chart.

The starter template we cloned at the beginning has the files public/vote.html and public/js/vote.js. These files already contain code to display a form in the web page and javascript to post to the server. I skipped writing that code here because it’s a basic HTML form and JavaScript to send form data to the server. Do leave a comment if anything there is confusing.

The chart will be displayed in index.html inside the public folder. index.html already contains script tags for Chart.js and Hamoni Sync (see lines 17 and 18)


    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
    <script src="https://unpkg.com/hamoni-sync@0.2.0/hamoni.dev.js"></script>
Enter fullscreen mode Exit fullscreen mode

Render the chart

Open the file public/index.js. Add the function below to render a chart in the web page.


    function renderChart(state) {
      var ctx = document.getElementById("myChart").getContext("2d");
      var chart = new Chart(ctx, {
        // The type of chart we want to create
        type: "bar",
        // The data for our dataset
        data: {
          labels: state.map(s => s.candidate),
          datasets: [
            {
              label: "Elections 2018",
              backgroundColor: "rgb(255, 99, 132)",
              borderColor: "rgb(255, 99, 132)",
              data: state.map(s => s.vote)
            }
          ]
        },
        // Configuration options go here
        options: {
          scales: {
            xAxes: [
              {
                time: {
                  unit: "Vote"
                },
                gridLines: {
                  display: false
                },
                ticks: {
                  maxTicksLimit: 6
                }
              }
            ],
            yAxes: [
              {
                ticks: {
                  min: 0,
                  max: 30,
                  maxTicksLimit: 10
                },
                gridLines: {
                  display: true
                }
              }
            ]
          },
          legend: {
            display: true
          }
        }
      });
    }
Enter fullscreen mode Exit fullscreen mode

This function takes a parameter that represents our chart state. The type options specify the type of chart we want to render, in this case, a bar chart. The data option defines properties used to display data for the chart. There are two important properties that I want to bring to your attention. First is the label property on line 8, labels: state.map(s => s.candidate)
It gets its value from the state. The chart state is an array of each electoral candidate and their vote. With that code, we’re setting the candidate’s name as labels for the chart. The second is data: state.map( s => s.vote) on line 14. It sets the data for the chart.

Now we need to retrieve chart state and render the chart.

Get state and state updates

To retrieve the chart state, we need to connect to Hamoni Sync. Add the following code to get state and state updates, then render the chart based on that:

let hamoni = new Hamoni("Account_ID", "APP_ID");
hamoni
  .connect()
  .then(response => {
    hamoni
      .get("election")
      .then(statePrimitive => {
        renderChart(statePrimitive.get());

        statePrimitive.onUpdated(state => renderChart(state));
      })
      .catch(console.log);
  })
  .catch(error => console.log(error));
Enter fullscreen mode Exit fullscreen mode

If the client successfully connects to Hamoni Sync, we call hamoni.get("election") to get our election state. If it succeeds, we call renderCharts() with the value for the state.

To be notified of state updates, we call onUpdated() on the state primitive with a callback that should be executed each time there’s an update.

Now we can test our code to see it working. Open the command line and run npm start, then open your browser and navigate to localhost:5000.

Voila!! 🚀

Real-time chart state made with less hassle. Chart.js is easy to use. Hamoni Sync reduces the development time and effort in designing complex state logic around publish/subscribe systems because it embraces the concept of state.

You can get the finished source code on GitHub.

Links

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .