34
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...
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:
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 needcd 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
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
Its time we integrate this with React!
response
variable now.Its time we integrate this with React!
At first it might look a bit intimidating but
Its a function that returns a stateful value, and a function that updates it.
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 thingsif(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 timeThe 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.
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
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
Now to make sure we don't load an empty graph before we actually get the data back from the server, we use 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 operatorreturn (
<div className="wrapper">
{loading?(
<p>
loading
</p>
):(
<Plot
{...data}
/>
)}
</div>
)
Now that we have everything in place, this is what it should look like:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect, useState } from "react"; | |
import { io } from "socket.io-client"; | |
import Plot from 'react-plotly.js'; | |
const App = () => { | |
const [data, setData] = useState(); | |
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(); | |
}, []); | |
return ( | |
<div className="wrapper"> | |
{loading?( | |
<p> | |
Loading... | |
</p> | |
):( | |
<Plot | |
{...data} | |
/> | |
)} | |
</div> | |
) | |
} | |
export default App |
Adding some basic styling to the body and making the graph cover the entire screen
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
margin: 0; | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', | |
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', | |
sans-serif; | |
-webkit-font-smoothing: antialiased; | |
-moz-osx-font-smoothing: grayscale; | |
} | |
.js-plotly-plot, | |
.plot-container { | |
height: 100%; | |
width: 100%; | |
} | |
.wrapper{ | |
height: 100vh; | |
overflow:hidden; | |
} |
This is how the end result should look like

Link to Github Repository
Cover Photo by Tech Daily on Unsplash
34