ROOT Real-Time plotting with multiple lines in a single plot
In our previous example we showed how to make a high performance realtime-updating plot with a ZMQ based datasource.
This example expands on that by not plotting one but five separate lines in a single plot. It expects to receive a double[5]
array via ZMQ, which is distributed to the 5 lines.
Compile using
g++ -o plot main.cpp `root-config --cflags --glibs` -lzmq
g++ -o server server.cpp `root-config --cflags --glibs` -lzmq
then run ./server
and ./plot
in separate shells.
Plotting client
#include <iostream>
#include <vector>
#include <array>
#include <zmq.hpp>
#include <TApplication.h>
#include <TGraph.h>
#include <TCanvas.h>
#include <TSystem.h>
using namespace std;
// Maximum number of data points to display
const int MAX_POINTS = 10000;
const int BATCH_SIZE = 10; // Only update plot every 100 points
const int NUM_SINEWAVES = 5;
int main(int argc, char* argv[]) {
// Initialize ROOT TApplication for event handling and GUI
TApplication app("app", &argc, argv);
// Create a canvas to display the graph
TCanvas *c1 = new TCanvas("c1", "Real-time Plot", 800, 600);
c1->SetGrid();
// Create a vector of TGraphs to hold the data
std::vector<std::unique_ptr<TGraph>> graphs(NUM_SINEWAVES);
for (int i = 0; i < NUM_SINEWAVES; ++i) {
graphs[i] = std::make_unique<TGraph>();
graphs[i]->SetTitle("Real-time Data;Time;Value");
graphs[i]->SetLineColor(i + 1); // Different color for each line
if (i == 0) {
graphs[i]->Draw("AL");
} else {
graphs[i]->Draw("L SAME");
}
}
// Setup ZeroMQ context and SUB socket
zmq::context_t context(1);
zmq::socket_t socket(context, zmq::socket_type::sub);
// Connect to the publisher socket (modify "tcp://localhost:5555" as needed)
socket.connect("tcp://localhost:5555");
// Subscribe to all messages (empty string filter)
socket.set(zmq::sockopt::subscribe, "");
// Variables to store data
double time = 0.0;
std::vector<double> times;
std::vector<std::array<double, NUM_SINEWAVES>> values;
int batch_counter = 0;
while (true) {
// Poll for incoming data with a timeout of 100 ms
zmq::message_t message;
if (socket.recv(message, zmq::recv_flags::none)) {
// Extract double value from the message
std::array<double, NUM_SINEWAVES> value;
memcpy(&value, message.data(), sizeof(value));
// Update the time (or you could use an external clock/timestamp)
time += 1.0 / 1000.0; // Assuming a 1kHz sample rate
// Add the new point to the vectors
times.push_back(time);
values.push_back(value);
// If we exceed the maximum number of points, remove the oldest point
if (times.size() > MAX_POINTS) {
times.erase(times.begin());
values.erase(values.begin());
}
// Only update the graph every BATCH_SIZE points
batch_counter++;
if (batch_counter >= BATCH_SIZE) {
batch_counter = 0;
// Update the TGraph with the new data
for (size_t i = 0; i < NUM_SINEWAVES; i++)
{
graphs[i]->Set(times.size());
for (size_t j = 0; j < times.size(); ++j) {
graphs[i]->SetPoint(j, times[j], values[j][i]);
}
}
// Redraw the graph
c1->Modified();
c1->Update();
}
}
// Handle ROOT events (this keeps the GUI responsive)
gSystem->ProcessEvents();
// Introduce a small delay (optional)
gSystem->Sleep(1); // 1 ms sleep for smooth handling
}
// Enter the ROOT event loop (never reached in this case, but necessary for completeness)
app.Run();
return 0;
}
Data generation server
#include <iostream>
#include <zmq.hpp>
#include <chrono>
#include <thread>
#include <cmath>
#include <array>
// Constants for sine wave generation
const double PI = 3.14159265358979323846;
const double SAMPLE_RATE = 1000.0; // Sample rate in Hz
const double FREQUENCY = 1.0; // 1Hz sine wave frequency
const int NUM_SINE_WAVES = 100; // Number of sine waves
int main() {
// Setup ZeroMQ context and PUB socket
zmq::context_t context(1);
zmq::socket_t socket(context, zmq::socket_type::pub);
// Bind to a port (e.g., tcp://*:5555)
socket.bind("tcp://*:5555");
// Start sine wave generation and publishing
int sample_count = 0;
std::array<double, NUM_SINE_WAVES> sine_values;
while (true) {
// Generate the next sine wave values
for (int i = 0; i < NUM_SINE_WAVES; ++i) {
double time = static_cast<double>(sample_count) / SAMPLE_RATE; // time in seconds
sine_values[i] = std::sin(2.0 * PI * (FREQUENCY + i) * time); // Different frequency for each sine wave
}
// Convert sine values to a message (sending an array of doubles)
zmq::message_t message(sine_values.size() * sizeof(double));
memcpy(message.data(), sine_values.data(), sine_values.size() * sizeof(double));
// Publish the sine wave values
socket.send(message, zmq::send_flags::none);
// Increment the sample count
sample_count++;
// Sleep to maintain 1kHz sample rate (1000 samples per second)
std::this_thread::sleep_for(std::chrono::microseconds(1000)); // 1 millisecond delay
if(sample_count % 1000 == 0) {
std::cout << "Sample count: " << sample_count << std::endl;
}
}
return 0;
}