C++ 1kHz+ Real-Time plotting using ROOT
The following example consists of two components:
- A C++ server which generates sine wave data (1Hz sine wave frequency, 1kHz samplerate = datarate) in real time, sending out each invidual data point using a ZeroMQ PUB socket on port
5555
. - A C++ client which receives the data and plots it in real time using ROOT, with a approximately 100 Hz update rate and a maximum of 10k data points in memory.
The server is implemented in server.cpp
and the client in main.cpp
.
Given an appropriate OpenGL backend, the client will display a real-time plot of the sine wave without noticable delay.
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 <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 10 points (100Hz update rate @1kHz)
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 TGraph to hold the data
auto graph = std::make_unique<TGraph>();
graph->SetTitle("Real-time Data;Time;Value");
//graph->SetMarkerStyle(7); // Small dot marker
graph->Draw("AL");
// 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<double> 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
double value;
memcpy(&value, message.data(), sizeof(double));
// 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
graph->Set(times.size());
for (size_t i = 0; i < times.size(); ++i) {
graph->SetPoint(i, times[i], values[i]);
}
graph->Draw("AL");
// 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>
// Constants for sine wave generation
const double PI = 3.14159265358979323846;
const int SAMPLE_RATE = 1000; // 1kHz sampling rate
const double FREQUENCY = 1.0; // 1Hz sine wave frequency
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;
while (true) {
// Generate the next sine wave value
double time = static_cast<double>(sample_count) / SAMPLE_RATE; // time in seconds
double sine_value = std::sin(2.0 * PI * FREQUENCY * time); // 1Hz sine wave
// Convert sine value to a message (sending a double)
zmq::message_t message(sizeof(double));
memcpy(message.data(), &sine_value, sizeof(double));
// Publish the sine wave value
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;
}