QCustomPlot Discussion and Comments

chart rendering performance (software/accelerated)Return to overview

i would like to talk about the overall chart painting performance (software / opengl renderer)
after looking at the lib code i see a lot of places where the performance can be improved.
as example i will use candlesticks chart
standard library implementation |drawCandlestickPlot| function when used with 60k candlesticks
has performance problems (opengl'ed not usable at all) when zooming / dragging
when looking at the drawing code it is expected to work like that.
there is no batching done, every candlestick is drawn using 3 calls - upper knot lower knot and the body
this gives 180k calls to draw this
knots drawn using drawline, body using draw rect - its better to draw line with higher width.

ive implemented own drawCandlestickPlot| function and the chart is fluid (opengl works even faster than software)
the drawing is done in just 4 calls! - it could be done in 2 calls if not for QPainter limitation
due to these limitations it isnt a ready-to-replace function as it has visual glitches because it draws all the reds and then greens
and ones are or top when zoomed out - when zoomed in - it looks the same
the QPainter's drawlines func dont allow to set every candlestick a different color(unlike opengl) - so cant batch whole chart

this patch (at the bottom of the post) is for 2.0.1 amalgated version from download

i would like to write this function using native opengl commands - it could be drawn using 2 calls - it would look same even when zoomed out
painter->beginNativePainting();
//gl code
painter->endNativePainting();

but at the moment im not sure if its possible - as i cant draw anything from inside the lib.
so here are my questions:
do you want to have specialized opengl drawing functions inside the lib?
- if opengl == true - draw using native gl
else - draw using normal QPainter
what would be needed to make it possible?

for maximum performance all charts shall be written as opengl funcs - but for most i guess batching can be done using QPainter - it still would increase performance alot!

ps. my setup:
ubuntu 18.10 x86-64, qt 5.12.1, qcustomplot 2.0.1

patch:

--- qcustomplot/unpatched/qcustomplot.cpp	2019-02-21 18:29:09.909776501 +0100
+++ qcustomplot/qcustomplot.cpp	2019-02-23 14:34:31.645877415 +0100
@@ -26813,36 +26813,80 @@
   QCPAxis *keyAxis = mKeyAxis.data();
   QCPAxis *valueAxis = mValueAxis.data();
   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
-
+
   if (keyAxis->orientation() == Qt::Horizontal)
   {
-    for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
+    double keyPixel;
+    double openPixel;
+    double closePixel;
+    double pixelWidth;
+
+    QPointF qpf1, qpf2;
+    QLineF l;
+    QVector<QLineF> redLines;
+    redLines.reserve(end-begin);
+    QVector<QLineF> greenLines;
+    greenLines.reserve(end-begin);
+
+    QVector<QLineF> redBody;
+    redBody.reserve(end-begin);
+    QVector<QLineF> greenBody;
+    greenBody.reserve(end-begin);
+
+    for(QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
     {
-      if (isSelected && mSelectionDecorator)
+      if(isSelected && mSelectionDecorator)
       {
         mSelectionDecorator->applyPen(painter);
         mSelectionDecorator->applyBrush(painter);
-      } else if (mTwoColored)
-      {
-        painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
-        painter->setBrush(it->close >= it->open ? mBrushPositive : mBrushNegative);
-      } else
+      }
+      else
       {
         painter->setPen(mPen);
         painter->setBrush(mBrush);
       }
-      double keyPixel = keyAxis->coordToPixel(it->key);
-      double openPixel = valueAxis->coordToPixel(it->open);
-      double closePixel = valueAxis->coordToPixel(it->close);
-      // draw high:
-      painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->high)), QPointF(keyPixel, valueAxis->coordToPixel(qMax(it->open, it->close))));
-      // draw low:
-      painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->low)), QPointF(keyPixel, valueAxis->coordToPixel(qMin(it->open, it->close))));
-      // draw open-close box:
-      double pixelWidth = getPixelWidth(it->key, keyPixel);
-      painter->drawRect(QRectF(QPointF(keyPixel-pixelWidth, closePixel), QPointF(keyPixel+pixelWidth, openPixel)));
+
+      keyPixel = keyAxis->coordToPixel(it->key);
+      openPixel = valueAxis->coordToPixel(it->open);
+      closePixel = valueAxis->coordToPixel(it->close);
+
+      qpf1.setX(keyPixel);
+      qpf1.setY(valueAxis->coordToPixel(it->high));
+      qpf2.setX(keyPixel);
+      qpf2.setY(valueAxis->coordToPixel(it->low));
+      l.setPoints(qpf1, qpf2);
+
+      it->close >= it->open ? greenLines.push_back(l) : redLines.push_back(l);
+
+      //draw open-close box:
+      if(it == begin)
+        pixelWidth = getPixelWidth(it->key, keyPixel);
+
+      qpf1.setX(keyPixel);
+      qpf1.setY(closePixel);
+      qpf2.setX(keyPixel);
+      qpf2.setY(openPixel);
+      l.setPoints(qpf1, qpf2);
+
+      it->close >= it->open ? greenBody.push_back(l) : redBody.push_back(l);
     }
-  } else // keyAxis->orientation() == Qt::Vertical
+    QPen b = mPenNegative;
+    b.setWidthF(mPenNegative.widthF()+pixelWidth);
+    painter->setPen(mPenNegative);
+    painter->setBrush(mBrushNegative);
+    painter->drawLines(redLines);
+    painter->setPen(b);
+    painter->drawLines(redBody);
+
+    QPen a = mPenPositive;
+    a.setWidthF(mPenPositive.widthF()+pixelWidth);
+    painter->setPen(mPenPositive);
+    painter->setBrush(mBrushPositive);
+    painter->drawLines(greenLines);
+    painter->setPen(a);
+    painter->drawLines(greenBody);
+  }
+  else // keyAxis->orientation() == Qt::Vertical
   {
     for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
     {


here are some benchmark numbers
candlestick chart (60k candlesticks) red/green

unmodified lib func - customPlot->setOpenGl(true, 4);
drawCandlestickPlot func took nanoseconds: 202900390 (ms: 202 )
unmodified lib func - opengl = false
drawCandlestickPlot func took nanoseconds: 189857351 (ms: 189 )

forum patch func - customPlot->setOpenGl(true, 4);
drawCandlestickPlot func took nanoseconds: 9275919 (ms: 9 )
forum patch func - opengl = false
drawCandlestickPlot func took nanoseconds: 10670431 (ms: 10 )

native opengl drawing - customPlot->setOpenGl(true, 4);
drawCandlestickPlot func took nanoseconds: 11835002 (ms: 11 )


yes ive hacked my way to draw with natve opengl - cant post it because im calling it from QQuickPaintedItem |paint func| so it wont work out of box, and yes im drawing charts inside qml.

when drawn with native opengl the chart looks exactly the same as unmodified lib.
its drawn 18 times faster and uses 20-30% less cpu
i think its worth the game.
would be nice to have it included in the library.

You can inherit from the QCPAbstractPlottable/QCPAbstractItem class. And override the method draw like this:

void draw(QCPPainter *painter)
{
  if (painter->paintEngine()->type() == QPaintEngine::OpenGL)
  {
    //draw with glDrawElements() etc.
  }
  else
  {
    //draw with QPainter API
  }
}

@miccs I am too drawing charts in qml. Painting is done from QQuickPaintedItem. So I am very interested how you hacked to draw with native opengl. It would be very helpful if you could post relevant code.

Thanks in advance.

ive made it faster even more, now it draws 60k candlesticks in just 2-3 ms. its about 100 times faster than standard liib implementation and uses 30-40% less cpu.

please remember that this patch makes only candlestick chart fast and only horizontal - if author of the lib would be interested in taking this patch i can make it work for both.
drawing selected lines is not implemented - as i dont need it - and again if author would show some interest then i would implement it as well.

so as QQuickPaintedItem is already opengled you can draw inside it using native opengl, in order to draw chart you need to make standard lib draw function as public and run it from there.
so your QQuickPaintedItem paint function shall look like this:

void qmlCustomPlot::paint(QPainter* _painter)
{
    if(customPlot)
    {
        this->setRenderTarget(QQuickPaintedItem::FramebufferObject);
        QPixmap picture( boundingRect().size().toSize() );
        QCPPainter qcpPainter( &picture );
        customPlot->toPainter( &qcpPainter );
        _painter->drawPixmap( QPoint(), picture );

        // draw chart bars from here for now
        // this painter works well with native opengl
        // should upstream fix library, then it be unnecessary
        candlesticks->draw(reinterpret_cast<QCPPainter*>(_painter));
    }
}

painting to frameBufferObject is neccesary!
for antialiasing put this in your constructor (QQuickPaintedItem)
this->setAntialiasing(true);//needs to be set!
customPlot->setOpenGl(true, 8);


and this is the patch for the lib (it patches amalgated version of the lib: header and cpp file as well)

Only in qcustomplot: changelog.txt
Only in qcustomplot: documentation
Only in qcustomplot: examples
Only in qcustomplot: GPL.txt
diff -u qcustomplot/unpatched/qcustomplot.cpp qcustomplot/qcustomplot.cpp
--- qcustomplot/unpatched/qcustomplot.cpp	2019-02-25 22:49:00.810308565 +0100
+++ qcustomplot/qcustomplot.cpp	2019-03-06 15:37:09.750065314 +0100
@@ -1121,11 +1121,13 @@
   {
     if (child->realVisibility())
     {
-      painter->save();
-      painter->setClipRect(child->clipRect().translated(0, -1));
-      child->applyDefaultAntialiasingHint(painter);
-      child->draw(painter);
-      painter->restore();
+        if (!dynamic_cast<QCPFinancial*>(child)) {
+          painter->save();
+          painter->setClipRect(child->clipRect().translated(0, -1));
+          child->applyDefaultAntialiasingHint(painter);
+          child->draw(painter);
+          painter->restore();
+        };
     }
   }
 }
@@ -26314,7 +26316,8 @@
   mBrushPositive(QBrush(QColor(50, 160, 0))),
   mBrushNegative(QBrush(QColor(180, 0, 15))),
   mPenPositive(QPen(QColor(40, 150, 0))),
-  mPenNegative(QPen(QColor(170, 5, 5)))
+  mPenNegative(QPen(QColor(170, 5, 5))),
+  invalidateVertexData(true)
 {
   mSelectionDecorator->setBrush(QBrush(QColor(160, 160, 255)));
 }
@@ -26461,6 +26464,11 @@
   mPenNegative = pen;
 }
 
+void QCPFinancial::setInvalidateVertexData(bool inv)
+{
+    invalidateVertexData = inv;
+}
+
 /*! \overload
   
   Adds the provided points in \a keys, \a open, \a high, \a low and \a close to the current data.
@@ -26810,39 +26818,143 @@
 */
 void QCPFinancial::drawCandlestickPlot(QCPPainter *painter, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, bool isSelected)
 {
+//  QElapsedTimer sw;
+//  sw.start();
   QCPAxis *keyAxis = mKeyAxis.data();
   QCPAxis *valueAxis = mValueAxis.data();
   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
-  
+
   if (keyAxis->orientation() == Qt::Horizontal)
   {
-    for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
-    {
-      if (isSelected && mSelectionDecorator)
-      {
-        mSelectionDecorator->applyPen(painter);
-        mSelectionDecorator->applyBrush(painter);
-      } else if (mTwoColored)
-      {
-        painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
-        painter->setBrush(it->close >= it->open ? mBrushPositive : mBrushNegative);
-      } else
-      {
-        painter->setPen(mPen);
-        painter->setBrush(mBrush);
-      }
-      double keyPixel = keyAxis->coordToPixel(it->key);
-      double openPixel = valueAxis->coordToPixel(it->open);
-      double closePixel = valueAxis->coordToPixel(it->close);
-      // draw high:
-      painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->high)), QPointF(keyPixel, valueAxis->coordToPixel(qMax(it->open, it->close))));
-      // draw low:
-      painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->low)), QPointF(keyPixel, valueAxis->coordToPixel(qMin(it->open, it->close))));
-      // draw open-close box:
-      double pixelWidth = getPixelWidth(it->key, keyPixel);
-      painter->drawRect(QRectF(QPointF(keyPixel-pixelWidth, closePixel), QPointF(keyPixel+pixelWidth, openPixel)));
+//    double pixelWidth = begin != end ? (getPixelWidth(begin->key, keyAxis->coordToPixel(begin->key))) : 0.0;
+    float barOCWidth = begin != end ? static_cast<float>(keyAxis->coordToPixel((end-1)->key + width() /2.0) - keyAxis->coordToPixel((end-1)->key - width()/2.0)) : 0.0f;
+    // line width is dependant on opengl vendor, here it is lower than width we request
+    // so cap what we can, we limit bar width when bar OHLC is equal
+    GLfloat lineWidthRange[2] = {0.0, 0.0};
+    glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, lineWidthRange);
+    // Maximum supported line width is in lineWidthRange[1].
+    // normalize barOCWidth
+    barOCWidth = barOCWidth > lineWidthRange[1] ? lineWidthRange[1] : barOCWidth;
+    barOCWidth = barOCWidth < lineWidthRange[0] ? lineWidthRange[0] : barOCWidth;
+
+    if (invalidateVertexData) {
+        unsigned int dataSize = static_cast<unsigned int>(end-begin);
+        vert_bar_lh.resize(dataSize*4);
+        vert_bar_oc.resize(dataSize*4);
+        vert_bar_c.resize(dataSize*6);
+
+   //     qDebug() << "size: " << vert_bar_lh.size() << " capacity: " << vert_bar_lh.capacity() << "\n";
+
+        QColor c[2]; int c_idx = 0;
+        c[0] = mPenPositive.color();
+        c[1] = mPenNegative.color();
+
+        GLfloat keyPixel;
+        float* p_lh = vert_bar_lh.data();
+        float* p_oc = vert_bar_oc.data();
+        GLubyte* p_c = vert_bar_c.data();
+        size_t i = 0, j = 0;
+        for(QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it, i+=4, j+=6)
+        {
+            if(isSelected && mSelectionDecorator)
+            {
+                mSelectionDecorator->applyPen(painter);
+                mSelectionDecorator->applyBrush(painter);
+            } else if (mTwoColored) {
+                it->close >= it->open ? c_idx = 0 : c_idx = 1;
+            }
+            else
+            {
+                painter->setPen(mPen);
+                painter->setBrush(mBrush);
+            }
+
+            keyPixel = static_cast<GLfloat>(keyAxis->coordToPixel(it->key));
+
+            if (it->low == it->high && it->low == it->open && it->low == it->close)
+            {
+                p_lh[i+0] = (static_cast<GLfloat>(keyPixel - barOCWidth / 2.0f));
+                p_lh[i+1] = (static_cast<GLfloat>(valueAxis->coordToPixel(it->low)));
+                p_lh[i+2] = (static_cast<GLfloat>(keyPixel + barOCWidth / 2.0f));
+                p_lh[i+3] = (static_cast<GLfloat>(valueAxis->coordToPixel(it->high)));
+            }
+            else
+            {
+                p_lh[i+0] = (keyPixel);
+                p_lh[i+1] = (static_cast<GLfloat>(valueAxis->coordToPixel(it->low)));
+                p_lh[i+2] = (keyPixel);
+                p_lh[i+3] = (static_cast<GLfloat>(valueAxis->coordToPixel(it->high)));
+            }
+
+            p_oc[i+0] = (keyPixel);
+            p_oc[i+1] = (static_cast<GLfloat>(valueAxis->coordToPixel(it->open)));
+            p_oc[i+2] = (keyPixel);
+            p_oc[i+3] = (static_cast<GLfloat>(valueAxis->coordToPixel(it->close)));
+
+            p_c[j+0] = (static_cast<GLubyte>(c[c_idx].red()));
+            p_c[j+1] = (static_cast<GLubyte>(c[c_idx].green()));
+            p_c[j+2] = (static_cast<GLubyte>(c[c_idx].blue()));
+           // vert_bar_c.push_back(c[c_idx].alpha());
+            p_c[j+3] = (static_cast<GLubyte>(c[c_idx].red()));
+            p_c[j+4] = (static_cast<GLubyte>(c[c_idx].green()));
+            p_c[j+5] = (static_cast<GLubyte>(c[c_idx].blue()));
+           // vert_bar_c.push_back(c[c_idx].alpha());
+        }
+        invalidateVertexData = false;
+    }
+
+    painter->beginNativePainting();
+    glPushMatrix();
+    glLoadIdentity();
+
+    // invert the y axis, down is positive
+    glScalef(1, -1, 1);
+    // mover the origin from the bottom left corner
+    // to the upper left corner
+    glTranslatef(0, -painter->window().height(), 0);
+
+    // draw bars lh line
+    glLineWidth(static_cast<GLfloat>(mPenNegative.widthF()));
+
+    glEnableClientState(GL_VERTEX_ARRAY);
+    glEnableClientState(GL_COLOR_ARRAY);
+
+    if (!vert_bar_lh.empty()) {
+        glVertexPointer(2, GL_FLOAT, 0, &vert_bar_lh[0]);
+        glColorPointer(3, GL_UNSIGNED_BYTE, 0, &vert_bar_c[0]);
+
+        glEnable( GL_MULTISAMPLE );
+        glDrawArrays(GL_LINES, 0, static_cast<int>(vert_bar_lh.size()) / 2);
+        glDisable( GL_MULTISAMPLE );
     }
-  } else // keyAxis->orientation() == Qt::Vertical
+
+    glDisableClientState(GL_VERTEX_ARRAY);
+    glDisableClientState(GL_COLOR_ARRAY);
+
+    // draw bars oc box
+    glLineWidth(static_cast<GLfloat>(barOCWidth));
+
+    glEnableClientState(GL_VERTEX_ARRAY);
+    glEnableClientState(GL_COLOR_ARRAY);
+
+    if (!vert_bar_oc.empty()) {
+        glVertexPointer(2, GL_FLOAT, 0, &vert_bar_oc[0]);
+        glColorPointer(3, GL_UNSIGNED_BYTE, 0, &vert_bar_c[0]);
+
+        glEnable( GL_MULTISAMPLE );
+        glDrawArrays(GL_LINES, 0, static_cast<int>(vert_bar_oc.size()) / 2);
+        glDisable( GL_MULTISAMPLE );
+    }
+
+    glDisableClientState(GL_VERTEX_ARRAY);
+    glDisableClientState(GL_COLOR_ARRAY);
+
+    glPopMatrix();
+
+    painter->endNativePainting();
+ //   qDebug() << "drawCandlestickPlot func took nanoseconds:" << sw.nsecsElapsed() << "(ms:" << sw.elapsed() << ")";
+  }
+  else // keyAxis->orientation() == Qt::Vertical
   {
     for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
     {
diff -u qcustomplot/unpatched/qcustomplot.h qcustomplot/qcustomplot.h
--- qcustomplot/unpatched/qcustomplot.h	2019-02-25 22:49:00.814308665 +0100
+++ qcustomplot/qcustomplot.h	2019-03-04 09:25:47.151191907 +0100
@@ -5916,6 +5916,7 @@
   void setBrushNegative(const QBrush &brush);
   void setPenPositive(const QPen &pen);
   void setPenNegative(const QPen &pen);
+  void setInvalidateVertexData(bool inv);
   
   // non-property methods:
   void addData(const QVector<double> &keys, const QVector<double> &open, const QVector<double> &high, const QVector<double> &low, const QVector<double> &close, bool alreadySorted=false);
@@ -5938,18 +5939,25 @@
   bool mTwoColored;
   QBrush mBrushPositive, mBrushNegative;
   QPen mPenPositive, mPenNegative;
+
+  // vertexes chart data for drawing
+  bool invalidateVertexData;
+  std::vector<GLfloat> vert_bar_lh, vert_bar_oc;
+  std::vector<GLubyte> vert_bar_c;
   
+public:
   // reimplemented virtual methods:
   virtual void draw(QCPPainter *painter) Q_DECL_OVERRIDE;
+  void getVisibleDataBounds(QCPFinancialDataContainer::const_iterator &begin, QCPFinancialDataContainer::const_iterator &end) const;
+protected:
   virtual void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const Q_DECL_OVERRIDE;
-  
   // non-virtual methods:
   void drawOhlcPlot(QCPPainter *painter, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, bool isSelected);
   void drawCandlestickPlot(QCPPainter *painter, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, bool isSelected);
   double getPixelWidth(double key, double keyPixel) const;
   double ohlcSelectTest(const QPointF &pos, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, QCPFinancialDataContainer::const_iterator &closestDataPoint) const;
   double candlestickSelectTest(const QPointF &pos, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, QCPFinancialDataContainer::const_iterator &closestDataPoint) const;
-  void getVisibleDataBounds(QCPFinancialDataContainer::const_iterator &begin, QCPFinancialDataContainer::const_iterator &end) const;
+//  void getVisibleDataBounds(QCPFinancialDataContainer::const_iterator &begin, QCPFinancialDataContainer::const_iterator &end) const;
   QRectF selectionHitBox(QCPFinancialDataContainer::const_iterator it) const;
   
   friend class QCustomPlot;
@@ -6371,7 +6379,9 @@
   QMargins mPadding;
   
   // reimplemented virtual methods:
+public:
   virtual void draw(QCPPainter *painter) Q_DECL_OVERRIDE;
+protected:
   virtual QPointF anchorPixelPosition(int anchorId) const Q_DECL_OVERRIDE;
   
   // non-virtual methods:
Only in qcustomplot: QCustomPlot.pri
Only in qcustomplot: unpatched

@Rikki-Tikki-Tavi yes it could be done like that - but i prefer to make it inside lib so everybody can use it out of box - if possible.

does anybody have an idea why i can draw using native opengl inside qcustomplot lib when the app is qwidget based (for example customplot's plot example - financial demo, the candlestick chart can be seen with my patched lib, and when i want to render it as QQuickPaintedItem like in the above post the native opengl cant be seen? (drawn using QPainter can be seen)

this->setRenderTarget(QQuickPaintedItem::FramebufferObject);
QPixmap picture( boundingRect().size().toSize() );
QCPPainter qcpPainter( &picture );
customPlot->toPainter( &qcpPainter );
_painter->drawPixmap( QPoint(), picture );

what is wrong with this approach?

i am having a hard time debugging this - as i dont know too well the internal structure / the way it works of the lib

qcustomplot.cpp

void QCPPaintBufferGlFbo::donePainting()
{
  static int count = 0;
  if (mGlFrameBuffer && mGlFrameBuffer->isBound())
  {
    std::string fileName("photos/testimg");
    fileName += std::to_string(count);
    fileName += ".png";
    mGlFrameBuffer->toImage().save(fileName.c_str(), "png");
    mGlFrameBuffer->release();
  }
  else
    qDebug() << Q_FUNC_INFO << "Either OpenGL frame buffer not valid or was not bound";
  count++;
}

this way i see that chart is actually rendered using native opengl calls

but in this func:
qcustomplot.cpp

QPixmap QCustomPlot::toPixmap(int width, int height, double scale)
{
  // this method is somewhat similar to toPainter. Change something here, and a change in toPainter might be necessary, too.
  int newWidth, newHeight;
  if (width == 0 || height == 0)
  {
    newWidth = this->width();
    newHeight = this->height();
  } else
  {
    newWidth = width;
    newHeight = height;
  }
  int scaledWidth = qRound(scale*newWidth);
  int scaledHeight = qRound(scale*newHeight);

  static int count = 0;
  QPixmap result(scaledWidth, scaledHeight);
  result.fill(mBackgroundBrush.style() == Qt::SolidPattern ? mBackgroundBrush.color() : Qt::transparent); // if using non-solid pattern, make transparent now and draw brush pattern later
  QCPPainter painter;
  painter.begin(&result);
  if (painter.isActive())
  {
    QRect oldViewport = viewport();
    setViewport(QRect(0, 0, newWidth, newHeight));
    painter.setMode(QCPPainter::pmNoCaching);
    if (!qFuzzyCompare(scale, 1.0))
    {
      if (scale > 1.0) // for scale < 1 we always want cosmetic pens where possible, because else lines might disappear for very small scales
        painter.setMode(QCPPainter::pmNonCosmetic);
      painter.scale(scale, scale);
    }

    if (mBackgroundBrush.style() != Qt::SolidPattern && mBackgroundBrush.style() != Qt::NoBrush) // solid fills were done a few lines above with QPixmap::fill
      painter.fillRect(mViewport, mBackgroundBrush);
    draw(&painter);
    setViewport(oldViewport);
    painter.end();
    std::string fileName("photos/testimg");
    fileName += std::to_string(count);
    fileName += ".png";
    result.toImage().save(fileName.c_str(), "png");
    count++;
  } else // might happen if pixmap has width or height zero
  {
    qDebug() << Q_FUNC_INFO << "Couldn't activate painter on pixmap";
    return QPixmap();
  }
  return result;
}

i cannot see the chart here - its not behind background - checked
how it is lost?
any idea?
@DerManu can you help a little?