QCustomPlot Discussion and Comments

Plot refresh rate issueReturn to overview

Hi,

First of all, I just wanted to acknowledge what a superb resource QCustomPlot is; I'm a commercial Qt user and the disparity in quality, flexibility and performance between this library and QtCharts is substantial.

I've referenced this previous post, and this example application (linked in the aforementioned post) to setup very basic QCustomPlot functionality within my existing QML-based application. I've used a QQuickPaintedItem class (as per the linked example application) and am able to configure a QCustomPlot graph within my QML application, as well as update the graph's data from c++.

However, the refresh rate of the graph itself is very slow; in my testing I'm seeing less than 1fps/Hz update rate. The data source for the graph is capped at an update frequency of 60Hz, and while I can see the replot() function (and subsequent afterReplot() slot function) being called at 60Hz, the QML plot only updates ~once per second. Before I post some code, I thought I'd point out that if I use a timer within my QQuickPaintedItem-based class, and add random data to the plot, the plot updates fluidly as expected. It's only when I'm using an external threaded class, and passing the data to the QQuickPaintedItem-based class do I have this refresh rate issue.

The below snippet is the setup of the QQuickPaintedItem-based class (myPlotClass); as you can see it's an amalgamation of the above linked QML example application and the real-time example application as provided in the QCustomPlot library source. As shown below, I'm using a timer to add data to the plot (not shown for brevity), which works as expected. The following snippet will show how I'd like to use QCustomPlot (as opposed to the timer method shown below).

void myPlotClass::initialiseClass()
{
    myCustomPlot = new QCustomPlot();

    myCustomPlot->setGeometry(0, 0, (int)width(), (int)height());
    myCustomPlot->setViewport(QRect(0, 0, (int)width(), (int)height()));

    myCustomPlot->addGraph();
    myCustomPlot->graph(0)->setPen(QPen("red"));

    QSharedPoint<QCPAxisTickerTime> timeTicker(new QCPAxisTickerTime);
    timeTicker->setTimeFormat("%h:%m:%s");
    myCustomPlot->xAxis->setTicker(timeTicker);
    myCustomPlot->axisRect()->setupFullAxesBox();

    myCustomPlot->yAxis->setRange(0, 100);
    
    startTimer(10);

    connect(myCustomPlot, &QCustomPlot::afterReplot, this, &myPlotClass::onCustomReplot);
    myCustomPlot->replot();
}

void myPlotClass::paint(QPainter *painter)
{
    if (myCustomPlot) {

        QPixmap       myPicture(boundingRect().size().toSize());
        QCPPainter    myPainter(&myPicture);

        myCustomPlot->toPainter(&myPainter);
        painter->drawPixmap(QPoint(), myPicture);
    }
}

In my QML file, I have a myPlotClass item, which sends a reference of itself (i.e. a myPlotClass object pointer) to an external class (workerClass). The workerClass class is on a different thread to the UI thread, as workerClass performs a multitude of relatively heavy processing tasks. Once workerClass has received the myPlotClass* pointer, it calls the above initialiseClass() function and establishes the signal/slot mechanism for sending the process data to myPlotClass for updating the plot.

void workerClass::receiveMyPlotClassReference(myPlotClass *receivedReference)
{
    //Assign the myPlotClass to a workerClass class pointer
    workerClassReference = receivedReference;

    //Connect the workerClass initialiseMyPlotClass() signal with the above initialiseClass() function
    connect(this, &workerClass:initialiseMyPlotClass, workerClassReference, &myPlotClass:initialiseClass);

    //Connect the workerClass sendData() signal with the myPlotClass receiveData() function
    connect(this, &workerClass::sendData, workerClassReference, &myPlotClass::receiveData);

    //Initialise myPlotClass
    emit initialiseMyPlotClass();
}

The above all works as expected. The below snippets show where workerClass processes and sends the data, and where myPlotClass receives the data and updates the graph.

void workerClass::prepareData()
{
    //Other processes...

    QVector<double> myVector {someKey, someValue};
    
    emit sendData(myVector);
}

void myPlotClass::receiveData(QVector<double> receivedData)
{
    if (myCustomPlot) {

        qDebug() << "About to replot...";

        myCustomPlot->graph(0)->addData(receivedData.at(0), receivedData.at(1));
        myCustomPlot->xAxis->setRange(receivedData.at(0), 100, Qt::AlignRight);
        myCustomPlot->replot();
    }
}

void myPlotClass::onCustomReplot()
{
    if (myCustomPlot) {

        qDebug() << "About to update...";
        update();
    }
}

As mentioned earlier, with the above signal/slot approach, I can see data being received at the expected 60Hz update rate (via both the qDebug() statements in receiveData() and onCustomReplot()). However, the actual updating of the plot in the GUI is very slow (<1fps).

I've tried other approaches as well, whereby I shared a buffer and a mutex between workerClass and myPlotClass, and then used the myPlotClass timer to routinely add data from the buffer. Again, this worked, but the GUI update rate was <1fps.

Does anyone have any ideas?

In summary
Updating the plot from within the myPlotClass timer timeout slot works perfectly, but any involvement of the threaded workerClass (either directly, by signal/slot or by a shared buffer) results in basically no performance at all.

I've figured this one out, it turns out I was providing the plot with incorrect data. As shown in my code above, I'm using a QCPAxisTickerTime xAxis, and had thought I was providing the plot with decimal point doubles. It turns out that I was only sending integers (i.e. 4, 5, 6 instead of 4.23, 5.47, 6.23, etc.), which caused the plot to only appear to update once a second. This was also due to using a very broad range (800 seconds); when I reduced this to 30 seconds, I could see the plot updating in real-time with flat sections between each second tick.

I've corrected my double formula and the plot is updating in real-time as expected.