JKQTPlotter: improved plotting speed for line-graphs by drawing a series of single lines instead of a polyline in some cases

This commit is contained in:
jkriege2 2022-08-30 22:24:24 +02:00
parent abd62dc341
commit 4ef29635ea
16 changed files with 116 additions and 34 deletions

View File

@ -33,6 +33,7 @@ Changes, compared to \ref page_whatsnew_V4_0_0 "v4.0.0" include:
<li>NEW/BREAKING CHANGE: changed JKQTPColorDerivationMode into a struct, which extends its capabilities above the previously available few enum-items</li> <li>NEW/BREAKING CHANGE: changed JKQTPColorDerivationMode into a struct, which extends its capabilities above the previously available few enum-items</li>
<li>NEW: added debug-feature to show boxes around text in the plot</li> <li>NEW: added debug-feature to show boxes around text in the plot</li>
<li>BREAKING: Print-Support can now be switched off with a CMAKE-option JKQtPlotter_BUILD_FORCE_NO_PRINTER_SUPPORT=ON. This also switches off PDF and SVG export, partly solves issue <a href="https://github.com/jkriege2/JKQtPlotter/issues/81">#81</a>, many thanks to <a href="https://github.com/sufuk">user:sufuk</a> for contributing part of the code and supplying the idea! </li> <li>BREAKING: Print-Support can now be switched off with a CMAKE-option JKQtPlotter_BUILD_FORCE_NO_PRINTER_SUPPORT=ON. This also switches off PDF and SVG export, partly solves issue <a href="https://github.com/jkriege2/JKQtPlotter/issues/81">#81</a>, many thanks to <a href="https://github.com/sufuk">user:sufuk</a> for contributing part of the code and supplying the idea! </li>
<li>NEW: improved plotting speed for line-graphs by drawing a series of single lines instead of a polyline in some cases</li>
<li>NEW: improved plotting speed for line-graphs by a compression algorithm (see JKQTPGraphLinesCompressionMixin) that removes overlaying lines (e.g. in JKQTPXYLineGraph)</li> <li>NEW: improved plotting speed for line-graphs by a compression algorithm (see JKQTPGraphLinesCompressionMixin) that removes overlaying lines (e.g. in JKQTPXYLineGraph)</li>
<li>NEW: improved plotting speed for line-graphs by a clipping algorithm (applies to JKQTPXYLineGraph, JKQTPGraphErrorStyleMixin, JKQTPSpecialLineHorizontalGraph, JKQTPSpecialLineVerticalGraph and others)</li> <li>NEW: improved plotting speed for line-graphs by a clipping algorithm (applies to JKQTPXYLineGraph, JKQTPGraphErrorStyleMixin, JKQTPSpecialLineHorizontalGraph, JKQTPSpecialLineVerticalGraph and others)</li>
<li>NEW: improved plotting speed for scatter-graphs by not calling draw functions for symbols outside the plot window (e.g. in JKQTPXYLineGraph)</li> <li>NEW: improved plotting speed for scatter-graphs by not calling draw functions for symbols outside the plot window (e.g. in JKQTPXYLineGraph)</li>

View File

@ -41,13 +41,16 @@ SpeedTestPlot::SpeedTestPlot():
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);
graph->setSymbolType(JKQTPNoSymbol);
addGraph(graph); addGraph(graph);
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"));
graph2->setLineWidth(1); graph2->setLineWidth(2);
graph2->setSymbolType(JKQTPNoSymbol);
addGraph(graph2); addGraph(graph2);
// 6. scale the plot so the graph is contained // 6. scale the plot so the graph is contained
@ -87,7 +90,7 @@ SpeedTestPlot::SpeedTestPlot():
actSymbols=new QAction(QObject::tr("Show Graph Symbols")); actSymbols=new QAction(QObject::tr("Show Graph Symbols"));
actSymbols->setCheckable(true); actSymbols->setCheckable(true);
actSymbols->setChecked(true); actSymbols->setChecked(false);
connect(actSymbols, &QAction::toggled, std::bind([](bool enabled, JKQTPXYLineGraph* g, JKQTPXYLineGraph* g2,SpeedTestPlot* p){ connect(actSymbols, &QAction::toggled, std::bind([](bool enabled, JKQTPXYLineGraph* g, JKQTPXYLineGraph* g2,SpeedTestPlot* p){
g->setSymbolType(enabled?JKQTPCross:JKQTPNoSymbol); g->setSymbolType(enabled?JKQTPCross:JKQTPNoSymbol);
g2->setSymbolType(enabled?JKQTPCircle:JKQTPNoSymbol); g2->setSymbolType(enabled?JKQTPCircle:JKQTPNoSymbol);

View File

@ -23,7 +23,8 @@
JKQTPEnhancedPainter::JKQTPEnhancedPainter(QPaintDevice *device): JKQTPEnhancedPainter::JKQTPEnhancedPainter(QPaintDevice *device):
QPainter(device) QPainter(device),
m_flags(DefaultPaintMode)
{ {
initQEnhacedPainter(); initQEnhacedPainter();
} }
@ -34,6 +35,51 @@ JKQTPEnhancedPainter::JKQTPEnhancedPainter():
initQEnhacedPainter(); initQEnhacedPainter();
} }
void JKQTPEnhancedPainter::setPainterFlag(PainterFlag flag, bool enabled)
{
if (!enabled && m_flags.testFlag(flag)) m_flags &= ~flag;
else if (enabled && !m_flags.testFlag(flag)) m_flags |= flag;
}
void JKQTPEnhancedPainter::setPainterFlag(PainterFlags flags)
{
m_flags=flags;
}
void JKQTPEnhancedPainter::drawPolylineFast(const QPointF *points, int pointCount)
{
if (!points || pointCount<2) return;
if (m_flags.testFlag(VectorPainting) || pen().style()!=Qt::SolidLine || pen().widthF()>device()->devicePixelRatioF()+0.01 || pen().widthF()<device()->devicePixelRatioF()*0.8) {
//qDebug()<<"drawFastPolyline(): widthF="<<pen().widthF();
drawPolyline(points, pointCount);
} else {
//qDebug()<<"drawFastPolyline()";
// we use a specialized algorithm only in a very defined circumstances
for (int i=1; i<pointCount; i++) {
QLineF l(points[i-1], points[i]);
if (!l.isNull() && l.length()>0) drawLine(l);
}
}
}
void JKQTPEnhancedPainter::drawPolylineFast(const QPoint *points, int pointCount)
{
if (!points || pointCount<2) return;
if (m_flags.testFlag(VectorPainting) || pen().style()!=Qt::SolidLine || pen().widthF()>1.01) {
drawPolyline(points, pointCount);
} else {
// we use a specialized algorithm only in a very defined circumstances
for (int i=1; i<pointCount; i++) {
QLine l(points[i-1], points[i]);
if (!l.isNull() && (l.dx()>0||l.dy()>0)) drawLine(l);
}
}
}
JKQTPEnhancedPainter::PainterFlags JKQTPEnhancedPainter::painterFlags() const {
return m_flags;
}
void JKQTPEnhancedPainter::initQEnhacedPainter() void JKQTPEnhancedPainter::initQEnhacedPainter()
{ {

View File

@ -32,17 +32,46 @@
*/ */
class JKQTCOMMON_LIB_EXPORT JKQTPEnhancedPainter : public QPainter { class JKQTCOMMON_LIB_EXPORT JKQTPEnhancedPainter : public QPainter {
public: public:
enum PainterFlag { DefaultPaintMode = 0x00, /*!< \brief the default mode, the JKQTPEnhancedPainter draws on a pixel-device */
VectorPainting = 0x01, /*!< \brief if set, the JKQTPEnhancedPainter draws onto a vector-device, like a printer, PDF or SVG-output */
};
Q_ENUMS(PainterFlag)
Q_FLAGS(PainterFlags)
Q_DECLARE_FLAGS(PainterFlags, PainterFlag)
JKQTPEnhancedPainter(QPaintDevice* device); JKQTPEnhancedPainter(QPaintDevice* device);
JKQTPEnhancedPainter(); JKQTPEnhancedPainter();
/** \copydoc m_flags */
PainterFlags painterFlags() const ;
/** \copydoc m_flags */
void setPainterFlag(PainterFlag flag, bool enabled=true);
/** \copydoc m_flags */
void setPainterFlag(PainterFlags flags);
/** \brief faster variant of QPainter::drawPolyline(),it turns out that drawing single lines is way faster than drawing with drawPolyLine()
*
* At least for thin (1 Pixel) lines on non-vector-devices, this does not make much difference in appearance
*/
void drawPolylineFast(const QPointF *points, int pointCount);
inline void drawPolylineFast(const QPolygonF &polyline) {
drawPolylineFast(polyline.constData(), int(polyline.size()));
}
void drawPolylineFast(const QPoint *points, int pointCount);
inline void drawPolylineFast(const QPolygon &polyline) {
drawPolylineFast(polyline.constData(), int(polyline.size()));
}
protected: protected:
void initQEnhacedPainter(); void initQEnhacedPainter();
private: private:
/** \brief flags, specifying how the JKQTPEnhancedPainter works
*
* \see PainterFlags */
PainterFlags m_flags;
}; };
Q_DECLARE_OPERATORS_FOR_FLAGS(JKQTPEnhancedPainter::PainterFlags)

View File

@ -126,7 +126,7 @@ void JKQTPContourPlot::draw(JKQTPEnhancedPainter &painter)
//qDebug()<<lineTranformed; //qDebug()<<lineTranformed;
} }
for (const QPolygonF& poly: contourLinesTransformedSingleLevel) { for (const QPolygonF& poly: contourLinesTransformedSingleLevel) {
painter.drawPolyline(poly); painter.drawPolylineFast(poly);
} }
} }
} }

View File

@ -299,14 +299,14 @@ void JKQTPEvaluatedFunctionWithErrorsGraphDrawingBase::drawXGraph(JKQTPEnhancedP
if (getDrawLine()) { if (getDrawLine()) {
painter.save(); auto __finalpaintline=JKQTPFinally([&painter]() {painter.restore();}); painter.save(); auto __finalpaintline=JKQTPFinally([&painter]() {painter.restore();});
painter.setPen(p); painter.setPen(p);
painter.drawPolyline(linePolygon); painter.drawPolylineFast(linePolygon);
} }
if (drawErrorLines && (static_cast<bool>(errorPlotFunction))) { if (drawErrorLines && (static_cast<bool>(errorPlotFunction))) {
painter.save(); auto __finalpainterrline=JKQTPFinally([&painter]() {painter.restore();}); painter.save(); auto __finalpainterrline=JKQTPFinally([&painter]() {painter.restore();});
painter.setPen(ep); painter.setPen(ep);
painter.drawPolyline(errorLineTop); painter.drawPolylineFast(errorLineTop);
painter.drawPolyline(errorLineBottom); painter.drawPolylineFast(errorLineBottom);
} }
@ -425,14 +425,14 @@ void JKQTPEvaluatedFunctionWithErrorsGraphDrawingBase::drawYGraph(JKQTPEnhancedP
if (getDrawLine()) { if (getDrawLine()) {
painter.save(); auto __finalpaintline=JKQTPFinally([&painter]() {painter.restore();}); painter.save(); auto __finalpaintline=JKQTPFinally([&painter]() {painter.restore();});
painter.setPen(p); painter.setPen(p);
painter.drawPolyline(linePolygon); painter.drawPolylineFast(linePolygon);
} }
if (drawErrorLines && (static_cast<bool>(errorPlotFunction))) { if (drawErrorLines && (static_cast<bool>(errorPlotFunction))) {
painter.save(); auto __finalpainterrline=JKQTPFinally([&painter]() {painter.restore();}); painter.save(); auto __finalpainterrline=JKQTPFinally([&painter]() {painter.restore();});
painter.setPen(ep); painter.setPen(ep);
painter.drawPolyline(errorLineTop); painter.drawPolylineFast(errorLineTop);
painter.drawPolyline(errorLineBottom); painter.drawPolylineFast(errorLineBottom);
} }

View File

@ -108,7 +108,7 @@ void JKQTPXYFunctionLineGraphBase::draw(JKQTPEnhancedPainter& painter) {
{ {
painter.save(); auto __finalpaintline=JKQTPFinally([&painter]() {painter.restore();}); painter.save(); auto __finalpaintline=JKQTPFinally([&painter]() {painter.restore();});
painter.setPen(p); painter.setPen(p);
painter.drawPolyline(data); painter.drawPolylineFast(data);
} }

View File

@ -466,14 +466,14 @@ void JKQTPFilledVerticalRangeGraph::draw(JKQTPEnhancedPainter &painter)
if (isHighlighted()) { if (isHighlighted()) {
painter.setPen(ps); painter.setPen(ps);
painter.drawPolyline(phigh); painter.drawPolylineFast(phigh);
painter.drawPolyline(plow); painter.drawPolylineFast(plow);
} }
painter.setPen(p); painter.setPen(p);
painter.drawPolyline(phigh); painter.drawPolylineFast(phigh);
painter.drawPolyline(plow); painter.drawPolylineFast(plow);
} }
} }
@ -581,14 +581,14 @@ void JKQTPFilledHorizontalRangeGraph::draw(JKQTPEnhancedPainter &painter)
if (isHighlighted()) { if (isHighlighted()) {
painter.setPen(ps); painter.setPen(ps);
painter.drawPolyline(phigh); painter.drawPolylineFast(phigh);
painter.drawPolyline(plow); painter.drawPolylineFast(plow);
} }
painter.setPen(p); painter.setPen(p);
painter.drawPolyline(phigh); painter.drawPolylineFast(phigh);
painter.drawPolyline(plow); painter.drawPolylineFast(plow);
} }
} }

View File

@ -142,7 +142,7 @@ void JKQTPGeoLine::draw(JKQTPEnhancedPainter& painter) {
if (drawHead) JKQTPPlotLineDecorator(painter, xx2.x(), xx2.y(), angle2, getHeadDecoratorStyle(), calcHeadDecoratorSize(getLinePen(painter, getParent()).widthF()));//, &lx2); if (drawHead) JKQTPPlotLineDecorator(painter, xx2.x(), xx2.y(), angle2, getHeadDecoratorStyle(), calcHeadDecoratorSize(getLinePen(painter, getParent()).widthF()));//, &lx2);
//points[0]=lx1; //points[0]=lx1;
//points[points.size()-1]=lx2; //points[points.size()-1]=lx2;
painter.drawPolyline(points.data(), points.size()); painter.drawPolylineFast(points.data(), points.size());
/*for (auto& p: points) { /*for (auto& p: points) {
JKQTPPlotSymbol(painter, p.x(), p.y(), JKQTPPlus, 5, 1, QColor("green"), QColor("darkgreen")); JKQTPPlotSymbol(painter, p.x(), p.y(), JKQTPPlus, 5, 1, QColor("green"), QColor("darkgreen"));
}*/ }*/
@ -421,7 +421,7 @@ void JKQTPGeoInfiniteLine::draw(JKQTPEnhancedPainter& painter) {
xx1=points[0]; xx1=points[0];
const QPointF xx1p=points[1]; const QPointF xx1p=points[1];
angle1=atan2(xx1p.y()-xx1.y(), xx1p.x()-xx1.x()); angle1=atan2(xx1p.y()-xx1.y(), xx1p.x()-xx1.x());
painter.drawPolyline(points.data(), points.size()); painter.drawPolylineFast(points.data(), points.size());
/*for (auto& p: points) { /*for (auto& p: points) {
JKQTPPlotSymbol(painter, p.x(), p.y(), JKQTPPlus, 5, 1, QColor("green"), QColor("darkgreen")); JKQTPPlotSymbol(painter, p.x(), p.y(), JKQTPPlus, 5, 1, QColor("green"), QColor("darkgreen"));
}*/ }*/
@ -592,7 +592,7 @@ void JKQTPGeoPolyLines::draw(JKQTPEnhancedPainter& painter) {
xx1=path[0]; xx1=path[0];
xx2=path[path.size()-1]; xx2=path[path.size()-1];
// draw corrected line // draw corrected line
painter.drawPolyline(path.data(), path.size()); painter.drawPolylineFast(path.data(), path.size());
doDrawDecorator=true; doDrawDecorator=true;
} else { } else {
// for non-linear axes, a line might not be drawn as a line, so we need to segment the line (i.e. linear function in coordinate space) // for non-linear axes, a line might not be drawn as a line, so we need to segment the line (i.e. linear function in coordinate space)
@ -607,7 +607,7 @@ void JKQTPGeoPolyLines::draw(JKQTPEnhancedPainter& painter) {
xx2=points_poly[points_poly.size()-1]; xx2=points_poly[points_poly.size()-1];
const QPointF xx2p=points_poly[points_poly.size()-2]; const QPointF xx2p=points_poly[points_poly.size()-2];
angle2=atan2(xx2p.y()-xx2.y(), xx2p.x()-xx2.x()); angle2=atan2(xx2p.y()-xx2.y(), xx2p.x()-xx2.x());
painter.drawPolyline(points_poly.data(), points_poly.size()); painter.drawPolylineFast(points_poly.data(), points_poly.size());
doDrawDecorator=true; doDrawDecorator=true;
/*for (auto& p: points_poly) { /*for (auto& p: points_poly) {
JKQTPPlotSymbol(painter, p.x(), p.y(), JKQTPPlus, 5, 1, QColor("green"), QColor("darkgreen")); JKQTPPlotSymbol(painter, p.x(), p.y(), JKQTPPlus, 5, 1, QColor("green"), QColor("darkgreen"));
@ -719,7 +719,7 @@ void JKQTPGeoArc::draw(JKQTPEnhancedPainter& painter) {
painter.save(); auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();}); painter.save(); auto __finalpaint=JKQTPFinally([&painter]() {painter.restore();});
painter.setPen(getLinePen(painter, parent)); painter.setPen(getLinePen(painter, parent));
painter.drawPolyline(rect); painter.drawPolylineFast(rect);
} }
void JKQTPGeoArc::setAngleStart(double __value) void JKQTPGeoArc::setAngleStart(double __value)

View File

@ -134,10 +134,10 @@ void JKQTPXYLineGraph::draw(JKQTPEnhancedPainter& painter) {
if (linesP.size()>0) { if (linesP.size()>0) {
if (isHighlighted()) { if (isHighlighted()) {
painter.setPen(penSelection); painter.setPen(penSelection);
painter.drawPolyline(linesP); painter.drawPolylineFast(linesP);
} }
painter.setPen(p); painter.setPen(p);
painter.drawPolyline(linesP); painter.drawPolylineFast(linesP);
} }
} }
} }

View File

@ -393,7 +393,7 @@ void JKQTPXYParametrizedScatterGraph::draw(JKQTPEnhancedPainter &painter)
} else { } else {
pp.setColor(getHighlightingLineColor()); pp.setColor(getHighlightingLineColor());
painter.setPen(pp); painter.setPen(pp);
painter.drawPolyline(linesP); painter.drawPolylineFast(linesP);
} }
} }
QPen pp=p; QPen pp=p;
@ -407,7 +407,7 @@ void JKQTPXYParametrizedScatterGraph::draw(JKQTPEnhancedPainter &painter)
} else { } else {
pp.setColor(getHighlightingLineColor()); pp.setColor(getHighlightingLineColor());
painter.setPen(pp); painter.setPen(pp);
painter.drawPolyline(linesP); painter.drawPolylineFast(linesP);
} }
} }

View File

@ -276,7 +276,7 @@ void JKQTPSpecialLineHorizontalGraph::draw(JKQTPEnhancedPainter& painter) {
painter.setBrush(Qt::NoBrush); painter.setBrush(Qt::NoBrush);
painter.setPen(ph); painter.setPen(ph);
for (const QPolygonF& lines : pl_fordrawing) { for (const QPolygonF& lines : pl_fordrawing) {
painter.drawPolyline(lines); painter.drawPolylineFast(lines);
} }
} }
@ -284,7 +284,7 @@ void JKQTPSpecialLineHorizontalGraph::draw(JKQTPEnhancedPainter& painter) {
painter.setBrush(Qt::NoBrush); painter.setBrush(Qt::NoBrush);
painter.setPen(p); painter.setPen(p);
for (const QPolygonF& lines : pl_fordrawing) { for (const QPolygonF& lines : pl_fordrawing) {
painter.drawPolyline(lines); painter.drawPolylineFast(lines);
} }
} }
@ -464,7 +464,7 @@ void JKQTPSpecialLineVerticalGraph::draw(JKQTPEnhancedPainter& painter) {
painter.setBrush(Qt::NoBrush); painter.setBrush(Qt::NoBrush);
painter.setPen(ph); painter.setPen(ph);
for (const QPolygonF& lines : pl_fordrawing) { for (const QPolygonF& lines : pl_fordrawing) {
painter.drawPolyline(lines); painter.drawPolylineFast(lines);
} }
} }
@ -472,7 +472,7 @@ void JKQTPSpecialLineVerticalGraph::draw(JKQTPEnhancedPainter& painter) {
painter.setBrush(Qt::NoBrush); painter.setBrush(Qt::NoBrush);
painter.setPen(p); painter.setPen(p);
for (const QPolygonF& lines : pl_fordrawing) { for (const QPolygonF& lines : pl_fordrawing) {
painter.drawPolyline(lines); painter.drawPolylineFast(lines);
} }
} }

View File

@ -1965,6 +1965,7 @@ void JKQTBasePlotter::printpreviewPaintRequested(QPrinter* printer) {
} }
JKQTPEnhancedPainter painter; JKQTPEnhancedPainter painter;
painter.setPainterFlag(JKQTPEnhancedPainter::VectorPainting);
painter.begin(printer); painter.begin(printer);
if (!printSetAbsolutePageSize) { if (!printSetAbsolutePageSize) {
#ifdef SHOW_JKQTPLOTTER_DEBUG #ifdef SHOW_JKQTPLOTTER_DEBUG
@ -2065,6 +2066,7 @@ void JKQTBasePlotter::printpreviewPaintRequestedNewPaintDevice(QPaintDevice *pai
if (printer) painter.begin(printer); if (printer) painter.begin(printer);
else if (svg) painter.begin(svg); else if (svg) painter.begin(svg);
else painter.begin(paintDevice); else painter.begin(paintDevice);
if (printer||svg) painter.setPainterFlag(JKQTPEnhancedPainter::VectorPainting);
#else #else
painter.begin(paintDevice); painter.begin(paintDevice);
#endif #endif
@ -3916,6 +3918,7 @@ void JKQTBasePlotter::copyPixelImage(bool showPreview) {
svg->setSize(size); svg->setSize(size);
svg->setOutputDevice(&buffer); svg->setOutputDevice(&buffer);
JKQTPEnhancedPainter painter; JKQTPEnhancedPainter painter;
painter.setPainterFlag(JKQTPEnhancedPainter::VectorPainting);
painter.begin(svg); painter.begin(svg);
painter.scale(factor,factor); painter.scale(factor,factor);
printAspect=printSizeY_Millimeter/printSizeX_Millimeter; printAspect=printSizeY_Millimeter/printSizeX_Millimeter;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 52 KiB