Real-Time Interactive Plotting Client-Side (using Sockets, React Hooks & Plotly)

Plotting real-time graphs might appear to be hard, especially if you don't know where to start.
Fortunately socket.io makes this ridiculously easy to do, especially if the server is handling most of the hard work...

In this article, I will explain the client-side implementation of a real-time plotting system. This is a second part to the series, so if you haven't read the server-side implementation, check it out here

I'll be making use of a few frameworks and libraries along the way, most importantly:

Prerequisites

First we need to setup a development environment using create-react-app

npx create-react-app real-time-plotting

After that, we need to cd into real-time-plotting and install a few additional libraries we need

cd real-time-plotting
npm install react-plotly.js plotly.js socket.io-client

We are good to go now! Start the development server using

npm start

Initialising a socket connection

We need to make sure that our client can establish a socket connection with the backend. For this we will be using the socket.io-client library. We also store our backend URL in an env file and declare it as REACT_APP_SOCKET_URL

import { io } from "socket.io-client";

const socketURL = process.env.REACT_APP_SOCKET_URL;
const socket = io(socketURL);

Now that we have a socket variable, we can listen to the on connect event and emit a graph request to the server.

socket.on("connect",()=>{
    socket.emit("ping_graph", {symbol: "ril.ns"});
});

Great! Now the server should be sending us the graph data on the event called graph-plot (refer to server-side implementation if you want to know how this works)

socket.on("graph_plot", res => {
    let response = JSON.parse(res);
});

We have the graph's data stored in the response variable now.
Its time we integrate this with React!

Using React's useEffect and useState Hooks

At first it might look a bit intimidating but useState is surprisingly easy to wrap your head around!
Its a function that returns a stateful value, and a function that updates it.

React's useEffect hook is used to run a particular function either after a complete render or when certain values get changed (by passing them in an array as a second argument)

This is going to be particularly handy since we need to ensure that our socket connection is established only once after the initial render.

const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
        const socketURL = process.env.REACT_APP_SOCKET_URL;
        const socket = io(socketURL);
        socket.on("connect",()=>{
            socket.emit("ping_graph", {symbol: "ril.ns"});
        });
        socket.on("graph_plot", res => {
            if(loading===true){
                setLoading(false);
            }
            let response = JSON.parse(res);
            response.config = {responsive: true}
            setData(response);
        });
        return () => socket.disconnect();
}, []);

As you see, a couple of things happened here

const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);

This basically sets two stateful variables loading and data where data is intially set to null and loading is sent to true

Inside the socket event listener for graph_plot, we do two important things

if(loading===true){
    setLoading(false);
}
setData(response);

The first statement is essentially an if statement that sets loading state as false when it runs for the first time

The second setData assigns the socket value we just got as the data

and lastly, we added a return statement inside useEffect.

return () => socket.disconnect();

This is known as a cleanup statement and is done to ensure that the socket connection is closed when the component is unmounted so we don't accidentally introduce memory leaks.

Plotting data using Plotly's React Component

This is the easiest step so far as it simply involves you to create a Plot with the data we get from the server.
Its as easy as

return (
    <div className="wrapper">
        <Plot
            {...data}
        />
    )}
    </div>
)

We use the spread operator to pass the data we got back as an object as props for the Plot component.
Now to make sure we don't load an empty graph before we actually get the data back from the server, we use the loading variable as the statement to a conditional operator

return (
    <div className="wrapper">
        {loading?(
            <p>
                loading
            </p>
        ):(
            <Plot
                {...data}
            />
        )}
    </div>
)

Finishing up

Now that we have everything in place, this is what it should look like:



Adding some basic styling to the body and making the graph cover the entire screen

This is how the end result should look like

Resources

Link to Github Repository
Cover Photo by Tech Daily on Unsplash

23