From 17b93ab580c08aebb3fe3412d356945b37d0ca4e Mon Sep 17 00:00:00 2001 From: jkriege2 Date: Tue, 23 Aug 2022 22:12:53 +0200 Subject: [PATCH] improved speed example with more options --- examples/speed/README.md | 24 +++++--- examples/speed/speedtestplot.cpp | 96 ++++++++++++++++++++++++----- examples/speed/speedtestplot.h | 18 ++++-- lib/jkqtplotter/jkqtpgraphsbase.cpp | 1 + 4 files changed, 110 insertions(+), 29 deletions(-) diff --git a/examples/speed/README.md b/examples/speed/README.md index 73ebfa3f8c..34d734eebf 100644 --- a/examples/speed/README.md +++ b/examples/speed/README.md @@ -1,5 +1,8 @@ # 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`](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 // 3. make data available to JKQTPlotter by adding it to the internal datastore. @@ -7,11 +10,11 @@ Data is stored in two [`std::array`](https://en.cppreference.com/ // 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(); - size_t columnX=ds->addColumn(X.data(), X.size(), "x"); - size_t columnY=ds->addColumn(Y.data(), Y.size(), "y"); - size_t columnY2=ds->addColumn(Y2.data(), Y2.size(), "y2"); + 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"); ``` -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: ```.cpp @@ -42,8 +45,8 @@ void SpeedTestPlot::plotNewData() // calculate and update FPS-rate in window title auto tlastalst=t_lastplot; t_lastplot=std::chrono::system_clock::now(); - double delta_secs=static_cast(std::chrono::duration_cast(t_lastplot-tlastalst).count())/1000.0; - setWindowTitle(QString("Live Data Speed Test: %2 datapoint, %1 fps").arg(1/delta_secs).arg(NDATA)); + const double delta_secs=static_cast(std::chrono::duration_cast(t_lastplot-tlastalst).count())/1000.0; + setWindowTitle(QString("Live Data Speed Test: %2 datapoint, %1 fps").arg(1.0/delta_secs).arg(NDATA)); // enqueue call for next data value 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) -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. You can configrue anti-aliasing with these calls: ```.cpp @@ -62,8 +65,11 @@ There are different facor affecting the replot speed: 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:
![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): diff --git a/examples/speed/speedtestplot.cpp b/examples/speed/speedtestplot.cpp index e6d32ca2ed..67feffb9fd 100644 --- a/examples/speed/speedtestplot.cpp +++ b/examples/speed/speedtestplot.cpp @@ -7,9 +7,8 @@ #include "speedtestplot.h" #include "jkqtplotter/graphs/jkqtpscatter.h" - 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) @@ -19,7 +18,7 @@ SpeedTestPlot::SpeedTestPlot(): // 2. now we create data for a simple plot (a sine curve + random[-0.5,0.5]) - for (size_t i=0; i(i)*dx; X[i]=x0+x; Y[i]=sin(x)+static_cast(std::rand())/static_cast(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 memory, oly uses the data stored in it! JKQTPDatastore* ds=getDatastore(); - size_t columnX=ds->addColumn(X.data(), X.size(), "x"); - size_t columnY=ds->addColumn(Y.data(), Y.size(), "y"); - size_t columnY2=ds->addColumn(Y2.data(), Y2.size(), "y2"); + 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: - JKQTPXYLineGraph* graph=new JKQTPXYLineGraph(this); + graph=new JKQTPXYLineGraph(this); graph->setXColumn(columnX); graph->setYColumn(columnY); graph->setTitle(QObject::tr("live sin() graph")); graph->setLineWidth(1); addGraph(graph); - JKQTPXYLineGraph* graph2=new JKQTPXYLineGraph(this); + graph2=new JKQTPXYLineGraph(this); graph2->setXColumn(columnX); graph2->setYColumn(columnY2); graph2->setTitle(QObject::tr("live cos() graph")); @@ -54,7 +53,7 @@ SpeedTestPlot::SpeedTestPlot(): setX(X[0], X[NDATA-1]); setY(-2,2); - actAntiAliase=new QAction("Anti-Aliase"); + actAntiAliase=new QAction(QObject::tr("Anti-Aliase")); actAntiAliase->setCheckable(true); actAntiAliase->setChecked(false); connect(actAntiAliase, &QAction::triggered, std::bind([](SpeedTestPlot* p){ @@ -63,20 +62,53 @@ SpeedTestPlot::SpeedTestPlot(): p->getPlotter()->setUseAntiAliasingForText(p->actAntiAliase->isChecked()); }, this)); - actTwoGraphs=new QAction("2 Graphs"); + actTwoGraphs=new QAction(QObject::tr("2 Graphs")); actTwoGraphs->setCheckable(true); actTwoGraphs->setChecked(true); connect(actTwoGraphs, &QAction::triggered, std::bind([](SpeedTestPlot* p, JKQTPXYLineGraph* g){ g->setVisible(p->actTwoGraphs->isChecked()); }, this, graph2)); - actFixedXAxis=new QAction("Fixed X-Axis"); + actFixedXAxis=new QAction(QObject::tr("Fixed X-Axis")); actFixedXAxis->setCheckable(true); 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(actTwoGraphs); addAction(actFixedXAxis); + addAction(menuSizes->menuAction()); + addAction(actTwoGraphs); + addAction(actLines); + addAction(actSymbols); // show plotter and make it a decent size show(); @@ -120,10 +152,44 @@ void SpeedTestPlot::plotNewData() redrawPlot(); // calculate and update FPS-rate in window title - auto tlastalst=t_lastplot; + const auto tlastalst=t_lastplot; t_lastplot=std::chrono::system_clock::now(); - double delta_secs=static_cast(std::chrono::duration_cast(t_lastplot-tlastalst).count())/1000.0; - setWindowTitle(QString("Live Data Speed Test: %2 datapoint, %1 fps").arg(1/delta_secs).arg(NDATA)); + const double delta_secs=static_cast(std::chrono::duration_cast(t_lastplot-tlastalst).count())/1000.0; + 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 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(i)*dx; + X[i]=x0+x; + Y[i]=sin(x)+static_cast(std::rand())/static_cast(RAND_MAX + 1u)-0.5; + Y2[i]=cos(x)+static_cast(std::rand())/static_cast(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); +} diff --git a/examples/speed/speedtestplot.h b/examples/speed/speedtestplot.h index cc229b5ff8..cafe865996 100644 --- a/examples/speed/speedtestplot.h +++ b/examples/speed/speedtestplot.h @@ -2,27 +2,35 @@ #include #include #include - +#include +#include #include "jkqtplotter/jkqtplotter.h" -#define NDATA 500 +class JKQTPXYLineGraph; // forward class SpeedTestPlot: public JKQTPlotter { Q_OBJECT protected: - std::array X, Y, Y2; - const double dx; + std::array X, Y, Y2; + double dx; double x0; + size_t NDATA; std::chrono::system_clock::time_point t_lastplot; QAction* actAntiAliase; QAction* actTwoGraphs; QAction* actFixedXAxis; + QAction* actLines; + QAction* actSymbols; + QMenu* menuSizes; + JKQTPXYLineGraph* graph; + JKQTPXYLineGraph* graph2; public: SpeedTestPlot(); virtual ~SpeedTestPlot(); - public slots: + protected slots: void plotNewData(); + void updateDataSize(size_t newSize); }; diff --git a/lib/jkqtplotter/jkqtpgraphsbase.cpp b/lib/jkqtplotter/jkqtpgraphsbase.cpp index 3c5d05bce6..19a0b6d14c 100644 --- a/lib/jkqtplotter/jkqtpgraphsbase.cpp +++ b/lib/jkqtplotter/jkqtpgraphsbase.cpp @@ -616,6 +616,7 @@ void JKQTPXYGraph::intSortData() if (parent==nullptr) return ; + if (sortData==JKQTPXYLineGraph::Unsorted) return ; JKQTPDatastore* datastore=parent->getDatastore(); int imin=0;