Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to repaint a QChart

Tags:

c++

qt

qtcharts

I'd like to know how to repaint a QChart after I append new points to the QLineSeries added to it. The goal is to use this for displaying data being acquired at high rates (up to 400 000 pts/sec) and updating the plot as the points arrive in packets.

Here's the test program I've been working on:

MainWindow:

class MainWindow : public QMainWindow{
    Q_OBJECT

    QLineSeries *series;
    QChart *chart;
    QChartView *chartView;

    int cnt=0;


public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_pB_Start_clicked();

private:
    Ui::MainWindow *ui;
};

MainWindow constructor:

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow){
    ui->setupUi(this);

    series = new QLineSeries();

    chart = new QChart();
    chart->setBackgroundRoundness(0);

    chart->addSeries(series);

 // A bunch of formatting
    chart->setBackgroundVisible(false);
    chart->setMargins(QMargins(0,0,0,0));
    chart->layout()->setContentsMargins(0,0,0,0);
    chart->legend()->hide();
    chart->setPlotAreaBackgroundBrush(QBrush(Qt::black));
    chart->setPlotAreaBackgroundVisible(true);
    chartView = new QChartView(chart);
    ui->gridLayout->addWidget(chartView);

}

And a pushButton clicked event to add points to the series:

void MainWindow::on_pB_Start_clicked(){
    series->append(cnt,qSin(cnt/10));
    cnt++;
    // Update plot here << ======== HOW?
}

The OpenGLSeries example does it somehow. I don't understand how. But that case it's a bit different as it replaces all points in the series with new ones, instead of appending them.

like image 734
A. Vieira Avatar asked Aug 06 '16 12:08

A. Vieira


2 Answers

Apparently QCharts doesn't need repaint(). Appending new points to the series seems to be enough. I wasn't seeing the data because I hadn't set the axis for the char and also because values weren't properly calculated.

Corrected code:

Header:

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow){
    ui->setupUi(this);

    series = new QLineSeries();

    chart = new QChart();    
    chart->addSeries(series);

    chart->createDefaultAxes(); // Preparing the axis
    chart->axisX()->setRange(0,10); 
    chart->axisY()->setRange(0,10); 

    // Same formatting
    chart->setBackgroundVisible(false);
    chart->setMargins(QMargins(0,0,0,0));
    chart->layout()->setContentsMargins(0,0,0,0);
    chart->legend()->hide();
    chart->setPlotAreaBackgroundBrush(QBrush(Qt::black));
    chart->setPlotAreaBackgroundVisible(true);
    chartView = new QChartView(chart);
    ui->gridLayout->addWidget(chartView);
}

And the pushButton code, casting cnt to double before calculation.

void MainWindow::on_pB_Start_clicked(){
    double val = 3*(qSin((double)cnt*2)+2);
    series->append(cnt,val); // Enough to trigger repaint!
    cnt++;
}
like image 114
A. Vieira Avatar answered Oct 11 '22 13:10

A. Vieira


First, if your will receive and append points at 400000 pts/sec in GUI thread, your application will get absolutely freezed. So you need to dedicate another thread to data receiving and processing, and send processed graphics data to the GUI thread using (for example) signals/slots connected with QueuedConnection. By "processing" I mean at least some sort of decimation (averaging, dropping, decimation as those DSP guys understand it), because 400000 pts/sec seems to fast, you'll waste your memory and GUI performance. But if you do not want to decimate, it's up to you. In this case you may consider a more lightweight data delivery mechanism than QueuedConnectioned signals/slots.

The second question is when to plot? Not so long ago I implemented similar functionality with QCustomPlot at much lower rates. The main problem I faced was a huge (and varying) lag when I tried to replot after receiving each point, especially while plotting antialiased graph. In your case the solution is to subclass QChartView (I suppose you have already done it), override timerEvent in it and call startTimer()/killTimer() when you need to start/stop replotting. Alternatively, you may hold a timer in an object which owns the QChartView object and issue replotting from there, but it looks like abstraction leakage in comparison to subclassing QChartView. All in all, this approach allows you to achieve almost constant framerate and make it as smooth as you want without freezing application's interface.

And finally, how to replot? QChartView seems to have repaint() method inherited from QWidget which does what you need.

like image 44
Sergey Avatar answered Oct 11 '22 15:10

Sergey