PDA

View Full Version : QChart slow when plotting large data signals



DAVC
22nd February 2019, 12:02
Hello.
I have a problem with QChart.
When i plot >3000 samples it goes incredibly slow. I tried plotting a signal with 16000 samples, which took 13 seconds to plot.

Here is the bit that is causing problems:



QLineSeries *lineseries = new QLineSeries();
QScatterSeries *scatterseries = new QScatterSeries();
QTime mytimer;
mytimer.start();
lineseries->append(ali[bandnr].toList()); // QVector<QPointF>
scatterseries->append(ali[bandnr].toList());
chart_->addSeries(lineseries);
chart_->addSeries(scatterseries);
qDebug() << "time: " << mytimer.elapsed();

scatterseries->setMarkerSize(8);
scatterseries->setPen(Qt::NoPen); // do not make border around points
QString name = "hello";
connect(scatterseries, &QScatterSeries::clicked, this, &LinePlot::keepCallout_);
connect(scatterseries, &QScatterSeries::hovered, [=](QPointF point, bool state) {this->tooltip_(point, state, name); });

"ali" is just a QVector<QVector<QPointF>>
I read somewhere that appending the data to the QLineSeries/QScatterSeries before adding them to QChart would improve speed. But its the same.
I also read that using "replace" instead of "append" should increase the speed. But it doesn't change anything.

The reason i also add a QScatterSeries is to make my "hover" tooltip only appear on actual data points.

I also tried QCustomPlot instead, which uses 0.142 seconds to plot the same data. Why is QChart that much slower? Is there a solution to make it faster?

Best Regards
DAVC

anda_skoa
22nd February 2019, 13:31
I also read that using "replace" instead of "append" should increase the speed. But it doesn't change anything.

Did you measure the time between these two steps?
The code you posted only checks the total.




I also tried QCustomPlot instead, which uses 0.142 seconds to plot the same data. Why is QChart that much slower? Is there a solution to make it faster?


Would probably require some profiling to see where the time is actually spent.

Cheers,
_

DAVC
22nd February 2019, 14:05
Thank you for your reply anda_skoa!

I think i might have misunderstood your request to begin with, but it actually returned some interesting results. I tried time the "addSeries" individually.




QLineSeries *lineseries = new QLineSeries();
QScatterSeries *scatterseries = new QScatterSeries();
QTime mytimer;
mytimer.start();
lineseries->append(ali[bandnr].toList()); // QVector<QPointF>
scatterseries->append(ali[bandnr].toList());
qDebug() << "pre add: " << mytimer.elapsed();
chart_->addSeries(lineseries);
qDebug() << "series: " << mytimer.elapsed();
chart_->addSeries(scatterseries);
qDebug() << "scatter: " << mytimer.elapsed();

scatterseries->setMarkerSize(8);
scatterseries->setPen(Qt::NoPen); // do not make border around points
QString name = "hello";
connect(scatterseries, &QScatterSeries::clicked, this, &LinePlot::keepCallout_);
connect(scatterseries, &QScatterSeries::hovered, [=](QPointF point, bool state) {this->tooltip_(point, state, name); });


And to my surprise:
Adding the QLineseries actually only takes 0.181 seconds.
adding the QScatterseries, however, takes 13.654 seconds.

It is QScatterSeries that is the culprit. Or maybe the fact that i am adding a second series to the same chart? I'll try switching it around.

EDIT:
I just tried to comment the line where i add QLineSeries. Adding QScatterSeries still takes about 13 seconds. Why does the two differ so much?

Damn, i thought i was being smart by making my hover signal attached to the QScatterSeries. I guess i'll have to find another way of doing it.


Best Regards DAVC

d_stranz
22nd February 2019, 16:31
Damn, i thought i was being smart by making my hover signal attached to the QScatterSeries. I guess i'll have to find another way of doing it.


Just a guess, but QScatterSeries might check every point to determine where the mouse is hovering, whereas QLineSeries might just check the bounding rectangle for the entire line.

BTW, QCustomPlot is really the way to go if you want to be able to customize anything about your plots. The way the QChart library is designed, there is absolutely no way to extend it to do something different from what its authors wrote. There are no virtual methods, so you can't derive something new from QScatterSeries, for example, to override the method to change the default behavior. You can't add a new plot type if the standard line, scatter, and other series don't work for your needs. What you see is what you get, and that's all you get.

QCustomPlot is completely customizable. I needed a scatter plot where each dot could have a different size, shape, and color, and where I could supply the data through a QAbstractItemModel without making a copy. I could do all of that with QCustomPlot and none of it with QChart.

DAVC
25th February 2019, 08:37
Thank you for your reply d_stranz!

However, with QLineSeries and QCustomPlot, I have the same issue with "hovering". That as soon as i just hover the line i get an arbitrary point. I only want to print exact data points, as in, plotted data is by sample. It doesn't make sense to plot y-value for sample 3.5 for example.

Do you have any good ideas for QCustomPlot to only make the tooltip appear when hovering an actual sample?

Best regards
DAVC

Thank you for your reply d_stranz!

However, with QLineSeries and QCustomPlot, I have the same issue with "hovering". That as soon as i just hover the line i get an arbitrary point. I only want to print exact data points, as in, plotted data is by sample. It doesn't make sense to plot y-value for sample 3.5 for example.

Do you have any good ideas for QCustomPlot to only make the tooltip appear when hovering an actual sample?

Best regards
DAVC

Added after 1 15 minutes:

Okay, so to anyone reading this with the sample problem: You want to plot large data signals and when you hover a given signal, you want a tooltip to appear with the value for an actual sample.

Do not use QScatterSeries. It is super slow. I don't know why.
Use QLineSeries.

Attach its "hovered" signal to a lambda function, like so:


connect(lineseries, &QLineSeries::hovered, [=](QPointF point, bool state) {this->tooltip_(point, state, name, ali[bandnr]); });

This allows you to capture the default parameters from &QLineSeries::hovered, and provide any other values you might want. I want the name and the data as well. My tooptip function looks like this:



void LinePlot::tooltip_(QPointF point, bool state, QString name, QVector<QPointF> data)
{
if (m_tooltip == 0)
m_tooltip = new Callout(chart_);

if (state) {
auto x = data[qRound(point.x())].x();
auto y = data[qRound(point.x())].y();
m_tooltip->setText(name + "\nX: " + QString::number(x) + " \nY: " + QString::number(y));
m_tooltip->setAnchor(point);
m_tooltip->setZValue(11);
m_tooltip->updateGeometry();
m_tooltip->setParentItem(chart_);
m_tooltip->show();
m_tooltip->update();
}
else {
m_tooltip->hide();
}
}


you can see the callout class here:
https://doc.qt.io/qt-5/qtcharts-callout-callout-cpp.html

This makes a tool tip appear when you hover an actual data point. It is a bit sluggish, tough, as the tooltip only disappears when you stop hovering the line and there is a timer for this. I'll see if i can remove that somehow.

I tried setting lineserie.SetPointsVisible(true) but that turns the plotting insanely slow again.

As for QCustomPlot. It is highly customizable, but when you're a rookie like me, it also becomes a bit overwhelming.

Best Regards
DAVC

d_stranz
25th February 2019, 17:47
Glad you solved it.


As for QCustomPlot. It is highly customizable, but when you're a rookie like me, it also becomes a bit overwhelming.

With great power comes great responsibility... and a learning curve. But Emanuel Eichhammer did a great job in designing an open and flexible widget that doesn't box you in to a fixed model. I just wish there was something similar for 3D plotting.

DAVC
26th February 2019, 12:12
With great power comes great responsibility... and a learning curve.


I was actually surprised at how quickly I got a plot. Documentation and forums are quite extensive. I did have a look at IM GUI (Immediate Mode GUI). Couldn't even figure out how to get a basic window after two days. Though that probably says more about me.

I would like to update my tooltip function:



void LinePlot::tooltip_(QPointF point, bool state, QString name, QVector<QPointF> data)
{
if (m_tooltip == 0)
m_tooltip = new Callout(chart_);

int x;
double y;
if (point.x() > 0){
x = data[qRound(point.x())].x();
y = data[x].y();
}
else {
x = 0;
y = data[x].y();
}
m_tooltip->setText(name + "\nX: " + QString::number(x) + " \nY: " + QString::number(y));
m_tooltip->setAnchor(QPointF(x,y));
m_tooltip->setZValue(11);
m_tooltip->updateGeometry();
m_tooltip->setParentItem(chart_);
m_tooltip->show();
m_tooltip->update();

if(!state) {
m_tooltip->hide();
}
}


It makes the tooltip more responsive. Now we print the tooltip as soon as we hover the line. Only print sample values. We anchor the tooltip to the sample values. We also update the tooltip while moving the mouse along the line.

Hope this helps someone.

Best regards
Davc

d_stranz
26th February 2019, 18:57
I was actually surprised at how quickly I got a plot.

Yes, if you want something standard, it is pretty simple. I have found that using OpenGL mode seems to have some problems. It could be my misunderstanding, but I had trouble getting plots to appear if I set the OpenGL flag.

garfungiloops
14th January 2020, 15:17
Hi,

another way how to solve this is the following "dirty" trick.
You can modify the QT source code for QLineSeries.
Search for "linechartitem.cpp", function "LineChartItem::paint".
Comment the line:

painter->drawLine(m_linePoints.at(i - 1), m_linePoints.at(i));

and add the following line instead:

painter->drawPoint( m_linePoints.at(i));

Then recompile the QT.
As the result you should get a scatter plot for QLineSeries which works fast ;-)

--
Cheers,
Garfungiloops

d_stranz
14th January 2020, 19:23
As the result you should get a scatter plot for QLineSeries which works fast ;-)

Why on earth would you want to do that? You could -never- create a true line plot where the points were connected by lines, you would always have a scatter plot no matter what. The only time this would look good is if your points occupied one x- and y-pixel after another. As soon as the x or y points were more than one pixel apart, you'd get a bunch of disconnected dots.