improved speed example with more options

This commit is contained in:
jkriege2 2022-08-23 22:12:53 +02:00
parent 676b97a3ca
commit 17b93ab580
4 changed files with 110 additions and 29 deletions

View File

@ -1,5 +1,8 @@
# Example (JKQTPlotter): Simple line-graph with live-data (speed test) {#JKQTPlotterSpeedTest} # Example (JKQTPlotter): Simple line-graph with live-data (speed test) {#JKQTPlotterSpeedTest}
This project (see `./examples/speed/`) simply creates a JKQTPlotter widget (as a new window) and adds two line-graph (a sine and a cosine wave). This project (see `./examples/speed/`) creates a JKQTPlotter widget (as a new window) withs two line-graph (a sine and a cosine wave with a random disturbance). It constantly changes the data (a bit) and redraws the plot. The redraw-rate is measured and displayed at the top of the window. Also the example offers several context-menu entries to alter the plot and thus experiment with different factors that affect the actuak plot speed.
Here follows a basic description of the eample's code. Note however that the actual code is more involved and this description is mostly to give you a first clue, what is going on.
Data is stored in two [`std::array<double, NDATA>`](https://en.cppreference.com/w/cpp/container/array) objects (`X`, `Y`, and `Y2`) and the data is added as external pointer to the datastore: Data is stored in two [`std::array<double, NDATA>`](https://en.cppreference.com/w/cpp/container/array) objects (`X`, `Y`, and `Y2`) and the data is added as external pointer to the datastore:
```.cpp ```.cpp
// 3. make data available to JKQTPlotter by adding it to the internal datastore. // 3. make data available to JKQTPlotter by adding it to the internal datastore.
@ -7,11 +10,11 @@ Data is stored in two [`std::array<double, NDATA>`](https://en.cppreference.com/
// the array is added to the datastore. therefore the datastore does not manage // the array is added to the datastore. therefore the datastore does not manage
// the memory, oly uses the data stored in it! // the memory, oly uses the data stored in it!
JKQTPDatastore* ds=getDatastore(); JKQTPDatastore* ds=getDatastore();
size_t columnX=ds->addColumn(X.data(), X.size(), "x"); size_t columnX=ds->addColumn(X.data(), NDATA, "x");
size_t columnY=ds->addColumn(Y.data(), Y.size(), "y"); size_t columnY=ds->addColumn(Y.data(), NDATA, "y");
size_t columnY2=ds->addColumn(Y2.data(), Y2.size(), "y2"); size_t columnY2=ds->addColumn(Y2.data(), NDATA, "y2");
``` ```
The datastore then uses the data from the `std::array` instances, but does not own their memory, i.e. also does not free it. This is useful, when data fro external sources should be used without copying. The datastore then uses the data from the `std::array` instances, i.e. just references the memory managed by the `std::arrays`s.
For this example we also don't use axis autoscaling, but set the axes explicitly: For this example we also don't use axis autoscaling, but set the axes explicitly:
```.cpp ```.cpp
@ -42,8 +45,8 @@ void SpeedTestPlot::plotNewData()
// calculate and update FPS-rate in window title // calculate and update FPS-rate in window title
auto tlastalst=t_lastplot; auto tlastalst=t_lastplot;
t_lastplot=std::chrono::system_clock::now(); t_lastplot=std::chrono::system_clock::now();
double delta_secs=static_cast<double>(std::chrono::duration_cast<std::chrono::milliseconds>(t_lastplot-tlastalst).count())/1000.0; const double delta_secs=static_cast<double>(std::chrono::duration_cast<std::chrono::milliseconds>(t_lastplot-tlastalst).count())/1000.0;
setWindowTitle(QString("Live Data Speed Test: %2 datapoint, %1 fps").arg(1/delta_secs).arg(NDATA)); setWindowTitle(QString("Live Data Speed Test: %2 datapoint, %1 fps").arg(1.0/delta_secs).arg(NDATA));
// enqueue call for next data value // enqueue call for next data value
QTimer::singleShot(1, this, SLOT(plotNewData())); QTimer::singleShot(1, this, SLOT(plotNewData()));
} }
@ -53,7 +56,7 @@ The result looks like this:
![speed1](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/speed.png) ![speed1](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/speed.png)
There are different facor affecting the replot speed: There are different factors affecting the replot speed:
1. *Anti-Aliasing:* If `JKQTPlotter` uses Anti-Aliasing for plotting, the plots are much nicer, but also about a factor of 3-4 slower. This is due to the increased amount of calculations, necessary in the drawing sub-system of Qt. 1. *Anti-Aliasing:* If `JKQTPlotter` uses Anti-Aliasing for plotting, the plots are much nicer, but also about a factor of 3-4 slower. This is due to the increased amount of calculations, necessary in the drawing sub-system of Qt.
You can configrue anti-aliasing with these calls: You can configrue anti-aliasing with these calls:
```.cpp ```.cpp
@ -62,8 +65,11 @@ There are different facor affecting the replot speed:
plot.getPlotter()->setUseAntiAliasingForText(false); plot.getPlotter()->setUseAntiAliasingForText(false);
``` ```
2. *Number of Graphs:* The number of plots (and also ther type and complexity) is a major imapct factor in the plotting speed. You can switch off a plot with the context menu:<br>![contextmenu_graph_visibility](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/contextmenu_graph_visibility.png) 2. *Number of Graphs:* The number of plots (and also ther type and complexity) is a major imapct factor in the plotting speed. You can switch off a plot with the context menu:<br>![contextmenu_graph_visibility](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/contextmenu_graph_visibility.png)
3. *Axis Scales and Plot Appearance:* Replotting is done in two steps: First the plot with the axes, labels etc. is drawn. Then the graphs are draw on top. Therefore a replot is faster, if only the graphs change, because the background (plot) does not have to be replotted. 3. *Number of Datapoints:* Of course, the more data has to be plotted, the slower the algorithm.
4. *Graph Type:* the speed also depends on how many elements have to be drawn. So drawing lines and symbols is slower than drawing lines-only or symbols-only.
5. *Axis Scales and Plot Appearance:* Replotting is done in two steps: First the plot with the axes, labels etc. is drawn. Then the graphs are draw on top. Therefore a replot is faster, if only the graphs change, because the background (plot) does not have to be replotted.
You can change all these properties by additional context-menu actions in this test-program.
The next table summarizes some results for plotting speed under different conditions, obatined with the test program in this directory (conditions: Qt 5.11, 32-bit, MinGW, Release, Win7, Phenom II X4 765, 500 data points): The next table summarizes some results for plotting speed under different conditions, obatined with the test program in this directory (conditions: Qt 5.11, 32-bit, MinGW, Release, Win7, Phenom II X4 765, 500 data points):

View File

@ -7,9 +7,8 @@
#include "speedtestplot.h" #include "speedtestplot.h"
#include "jkqtplotter/graphs/jkqtpscatter.h" #include "jkqtplotter/graphs/jkqtpscatter.h"
SpeedTestPlot::SpeedTestPlot(): SpeedTestPlot::SpeedTestPlot():
JKQTPlotter(), dx(1.0/double(NDATA)*4.0*JKQTPSTATISTICS_PI), x0(0) JKQTPlotter(), NDATA(500), dx(1.0/500.0*4.0*JKQTPSTATISTICS_PI), x0(0)
{ {
// 1. optimize JKQTPlotter for speed (by switching off anti-aliasing) // 1. optimize JKQTPlotter for speed (by switching off anti-aliasing)
@ -19,7 +18,7 @@ SpeedTestPlot::SpeedTestPlot():
// 2. now we create data for a simple plot (a sine curve + random[-0.5,0.5]) // 2. now we create data for a simple plot (a sine curve + random[-0.5,0.5])
for (size_t i=0; i<NDATA; i++) { for (size_t i=0; i<X.size(); i++) {
const double x=static_cast<double>(i)*dx; const double x=static_cast<double>(i)*dx;
X[i]=x0+x; X[i]=x0+x;
Y[i]=sin(x)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5; Y[i]=sin(x)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5;
@ -31,19 +30,19 @@ SpeedTestPlot::SpeedTestPlot():
// the array is added to the datastore. therefore the datastore does not manage // the array is added to the datastore. therefore the datastore does not manage
// the memory, oly uses the data stored in it! // the memory, oly uses the data stored in it!
JKQTPDatastore* ds=getDatastore(); JKQTPDatastore* ds=getDatastore();
size_t columnX=ds->addColumn(X.data(), X.size(), "x"); size_t columnX=ds->addColumn(X.data(), NDATA, "x");
size_t columnY=ds->addColumn(Y.data(), Y.size(), "y"); size_t columnY=ds->addColumn(Y.data(), NDATA, "y");
size_t columnY2=ds->addColumn(Y2.data(), Y2.size(), "y2"); size_t columnY2=ds->addColumn(Y2.data(), NDATA, "y2");
// 4. create two graphs in the plot, which plots the dataset X/Y: // 4. create two graphs in the plot, which plots the dataset X/Y:
JKQTPXYLineGraph* graph=new JKQTPXYLineGraph(this); graph=new JKQTPXYLineGraph(this);
graph->setXColumn(columnX); graph->setXColumn(columnX);
graph->setYColumn(columnY); graph->setYColumn(columnY);
graph->setTitle(QObject::tr("live sin() graph")); graph->setTitle(QObject::tr("live sin() graph"));
graph->setLineWidth(1); graph->setLineWidth(1);
addGraph(graph); addGraph(graph);
JKQTPXYLineGraph* graph2=new JKQTPXYLineGraph(this); graph2=new JKQTPXYLineGraph(this);
graph2->setXColumn(columnX); graph2->setXColumn(columnX);
graph2->setYColumn(columnY2); graph2->setYColumn(columnY2);
graph2->setTitle(QObject::tr("live cos() graph")); graph2->setTitle(QObject::tr("live cos() graph"));
@ -54,7 +53,7 @@ SpeedTestPlot::SpeedTestPlot():
setX(X[0], X[NDATA-1]); setX(X[0], X[NDATA-1]);
setY(-2,2); setY(-2,2);
actAntiAliase=new QAction("Anti-Aliase"); actAntiAliase=new QAction(QObject::tr("Anti-Aliase"));
actAntiAliase->setCheckable(true); actAntiAliase->setCheckable(true);
actAntiAliase->setChecked(false); actAntiAliase->setChecked(false);
connect(actAntiAliase, &QAction::triggered, std::bind([](SpeedTestPlot* p){ connect(actAntiAliase, &QAction::triggered, std::bind([](SpeedTestPlot* p){
@ -63,20 +62,53 @@ SpeedTestPlot::SpeedTestPlot():
p->getPlotter()->setUseAntiAliasingForText(p->actAntiAliase->isChecked()); p->getPlotter()->setUseAntiAliasingForText(p->actAntiAliase->isChecked());
}, this)); }, this));
actTwoGraphs=new QAction("2 Graphs"); actTwoGraphs=new QAction(QObject::tr("2 Graphs"));
actTwoGraphs->setCheckable(true); actTwoGraphs->setCheckable(true);
actTwoGraphs->setChecked(true); actTwoGraphs->setChecked(true);
connect(actTwoGraphs, &QAction::triggered, std::bind([](SpeedTestPlot* p, JKQTPXYLineGraph* g){ connect(actTwoGraphs, &QAction::triggered, std::bind([](SpeedTestPlot* p, JKQTPXYLineGraph* g){
g->setVisible(p->actTwoGraphs->isChecked()); g->setVisible(p->actTwoGraphs->isChecked());
}, this, graph2)); }, this, graph2));
actFixedXAxis=new QAction("Fixed X-Axis"); actFixedXAxis=new QAction(QObject::tr("Fixed X-Axis"));
actFixedXAxis->setCheckable(true); actFixedXAxis->setCheckable(true);
actFixedXAxis->setChecked(false); actFixedXAxis->setChecked(false);
actLines=new QAction(QObject::tr("Show Graph Lines"));
actLines->setCheckable(true);
actLines->setChecked(true);
connect(actLines, &QAction::toggled, std::bind([](bool enabled, JKQTPXYLineGraph* g, JKQTPXYLineGraph* g2){
g->setDrawLine(enabled);
g2->setDrawLine(enabled);
}, std::placeholders::_1, graph, graph2));
actSymbols=new QAction(QObject::tr("Show Graph Symbols"));
actSymbols->setCheckable(true);
actSymbols->setChecked(true);
connect(actSymbols, &QAction::toggled, std::bind([](bool enabled, JKQTPXYLineGraph* g, JKQTPXYLineGraph* g2){
g->setSymbolType(enabled?JKQTPCross:JKQTPNoSymbol);
g2->setSymbolType(enabled?JKQTPCircle:JKQTPNoSymbol);
}, std::placeholders::_1, graph, graph2));
menuSizes=new QMenu(QObject::tr("number of datapoints"), this);
QActionGroup* actGroup=new QActionGroup(menuSizes);
for (size_t size: {50,100, 500, 1000, 2000, 10000}) {
QAction* act=actGroup->addAction(QString::number(size));
act->setCheckable(true);
act->setChecked(size==500);
connect(act, &QAction::toggled, std::bind([](bool enabled,SpeedTestPlot* p, size_t size){
if (enabled) {
p->updateDataSize(size);
}
}, std::placeholders::_1, this, size));
menuSizes->addAction(act);
}
addAction(actAntiAliase); addAction(actAntiAliase);
addAction(actTwoGraphs);
addAction(actFixedXAxis); addAction(actFixedXAxis);
addAction(menuSizes->menuAction());
addAction(actTwoGraphs);
addAction(actLines);
addAction(actSymbols);
// show plotter and make it a decent size // show plotter and make it a decent size
show(); show();
@ -120,10 +152,44 @@ void SpeedTestPlot::plotNewData()
redrawPlot(); redrawPlot();
// calculate and update FPS-rate in window title // calculate and update FPS-rate in window title
auto tlastalst=t_lastplot; const auto tlastalst=t_lastplot;
t_lastplot=std::chrono::system_clock::now(); t_lastplot=std::chrono::system_clock::now();
double delta_secs=static_cast<double>(std::chrono::duration_cast<std::chrono::milliseconds>(t_lastplot-tlastalst).count())/1000.0; const double delta_secs=static_cast<double>(std::chrono::duration_cast<std::chrono::milliseconds>(t_lastplot-tlastalst).count())/1000.0;
setWindowTitle(QString("Live Data Speed Test: %2 datapoint, %1 fps").arg(1/delta_secs).arg(NDATA)); setWindowTitle(QString("Live Data Speed Test: %2 datapoint, %1 fps").arg(1.0/delta_secs,0,'f',2).arg(NDATA));
// enqueue call for next data value // enqueue call for next data value
QTimer::singleShot(1, this, SLOT(plotNewData())); QTimer::singleShot(1, this, SLOT(plotNewData()));
} }
void SpeedTestPlot::updateDataSize(size_t newSize)
{
NDATA=newSize;
dx=1.0/double(NDATA)*4.0*JKQTPSTATISTICS_PI;
// 2. now we create data for a simple plot (a sine curve + random[-0.5,0.5])
for (size_t i=0; i<X.size(); i++) {
const double x=static_cast<double>(i)*dx;
X[i]=x0+x;
Y[i]=sin(x)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5;
Y2[i]=cos(x)+static_cast<double>(std::rand())/static_cast<double>(RAND_MAX + 1u)-0.5;
}
// 3. make data available to JKQTPlotter by adding it to the internal datastore.
// Here the data from the std::array's is not copied, but only the pointer to
// the array is added to the datastore. therefore the datastore does not manage
// the memory, oly uses the data stored in it!
JKQTPDatastore* ds=getDatastore();
ds->clear();
size_t columnX=ds->addColumn(X.data(), NDATA, "x");
size_t columnY=ds->addColumn(Y.data(), NDATA, "y");
size_t columnY2=ds->addColumn(Y2.data(), NDATA, "y2");
// 4. create two graphs in the plot, which plots the dataset X/Y:
graph->setXColumn(columnX);
graph->setYColumn(columnY);
graph2->setXColumn(columnX);
graph2->setYColumn(columnY2);
// 6. scale the plot so the graph is contained
setX(X[0], X[NDATA]);
setY(-2,2);
}

View File

@ -2,27 +2,35 @@
#include <array> #include <array>
#include <random> #include <random>
#include <chrono> #include <chrono>
#include <QMenu>
#include <QActionGroup>
#include "jkqtplotter/jkqtplotter.h" #include "jkqtplotter/jkqtplotter.h"
#define NDATA 500 class JKQTPXYLineGraph; // forward
class SpeedTestPlot: public JKQTPlotter { class SpeedTestPlot: public JKQTPlotter {
Q_OBJECT Q_OBJECT
protected: protected:
std::array<double, NDATA> X, Y, Y2; std::array<double, 10000> X, Y, Y2;
const double dx; double dx;
double x0; double x0;
size_t NDATA;
std::chrono::system_clock::time_point t_lastplot; std::chrono::system_clock::time_point t_lastplot;
QAction* actAntiAliase; QAction* actAntiAliase;
QAction* actTwoGraphs; QAction* actTwoGraphs;
QAction* actFixedXAxis; QAction* actFixedXAxis;
QAction* actLines;
QAction* actSymbols;
QMenu* menuSizes;
JKQTPXYLineGraph* graph;
JKQTPXYLineGraph* graph2;
public: public:
SpeedTestPlot(); SpeedTestPlot();
virtual ~SpeedTestPlot(); virtual ~SpeedTestPlot();
public slots: protected slots:
void plotNewData(); void plotNewData();
void updateDataSize(size_t newSize);
}; };

View File

@ -616,6 +616,7 @@ void JKQTPXYGraph::intSortData()
if (parent==nullptr) return ; if (parent==nullptr) return ;
if (sortData==JKQTPXYLineGraph::Unsorted) return ;
JKQTPDatastore* datastore=parent->getDatastore(); JKQTPDatastore* datastore=parent->getDatastore();
int imin=0; int imin=0;