QCustomPlot Discussion and Comments

real-time signal monitorReturn to overview

Hello,

I want to make EEG real time viewer (up to 64 graphs, each can contain up to 2k points) using QCustomPlot and now i am experimenting with perfomance of realTimeDemo. But in my program i dont want graphs to eventually scrolls with new data comes. I want just redraw some portion of old data with new one and return to the left side of window, when right border of window is reached.

Question is if it is possible to repaint just some rectangle area, thus increasing perfomance and smoothness of drawing?

Thanks.

Solved by modifying QCustomPlot::replot and ::draw methods, so they accept some clipping rect as parameter.
Also, I render plot only with grid and axes to pixmap and use it as background, so I can save cpu time by rendering only data lines.
Thanks for great project.

Hi alexkarnaukhov,
Can you describe how you solved with code snippet?

Here is my modified QCustomPlot::replot():

void QCustomPlot::replot(QCustomPlot::RefreshPriority refreshPriority, bool dontClear, QRect replotRect)
{
  if (mReplotting) // incase signals loop back to replot slot
    return;
  mReplotting = true;
  emit beforeReplot();

  if(!dontClear)
  {
      mPaintBuffer.fill(mBackgroundBrush.style() == Qt::SolidPattern ? mBackgroundBrush.color() : Qt::transparent); // zalivka!
  }
  QCPPainter painter;
  painter.begin(&mPaintBuffer);
  if (painter.isActive())
  {
    painter.setRenderHint(QPainter::HighQualityAntialiasing); // to make Antialiasing look good if using the OpenGL graphicssystem
    if (mBackgroundBrush.style() != Qt::SolidPattern && mBackgroundBrush.style() != Qt::NoBrush)
      painter.fillRect(mViewport, mBackgroundBrush);

    if(dontClear)
    {
        painter.fillRect(replotRect,mBackgroundBrush.color());//Qt::white);
        painter.setClipRect(QRect(replotRect.topLeft(), replotRect.bottomRight()-QPoint(10,0)));
        painter.setClipping(1);
    }
    draw(&painter, replotRect);
    painter.end();
    if ((refreshPriority == rpHint && mPlottingHints.testFlag(QCP::phForceRepaint)) || refreshPriority==rpImmediate)
      repaint();
    else
      update();
  } else // might happen if QCustomPlot has width or height zero
    qDebug() << Q_FUNC_INFO << "Couldn't activate painter on buffer";
  
  emit afterReplot();
  mReplotting = false;
}

Here is modified QCustomPlot::draw():

void QCustomPlot::draw(QCPPainter *painter, QRect replotRect)
{
  // run through layout phases:
  mPlotLayout->update(QCPLayoutElement::upPreparation);
  mPlotLayout->update(QCPLayoutElement::upMargins);
  mPlotLayout->update(QCPLayoutElement::upLayout);
  
  // draw viewport background pixmap:
  drawBackground(painter);

  // draw all layered objects (grid, axes, plottables, items, legend,...):
  foreach (QCPLayer *layer, mLayers)
  {
    foreach (QCPLayerable *child, layer->children())
    {
      if (child->realVisibility())
      {
        painter->save();
        painter->setClipRect(child->clipRect().translated(0, -1), Qt::IntersectClip);
        child->applyDefaultAntialiasingHint(painter);
        child->draw(painter);
        painter->restore();
      }
    }
  }
  
  /* Debug code to draw all layout element rects
  foreach (QCPLayoutElement* el, findChildren<QCPLayoutElement*>())
  {
    painter->setBrush(Qt::NoBrush);
    painter->setPen(QPen(QColor(0, 0, 0, 100), 0, Qt::DashLine));
    painter->drawRect(el->rect());
    painter->setPen(QPen(QColor(255, 0, 0, 100), 0, Qt::DashLine));
    painter->drawRect(el->outerRect());
  }
  */
}

But you should carefully add new points on graphs using this function. You should correctly calculate size of clipping rect, so it would contain only new points, and then remove almost all points, which you dont want to be repainted. Also, you should render new background again after right border is reached. I do it like this:

//clear-set background if screen right edge is reached
    if(EEG_lastkey > EEG_lastkey1+EEGGraphs.at(0)->keyAxis()->range().size() || EEG_lastkey1==-1)
    {
        for(int j=0; j<EEGGraphs.count(); j++)
        {
            EEGGraphs.at(j)->clearData();
            EEGGraphs.at(j)->keyAxis()->setRange(EEG_lastkey, EEG_lastkey + EEGGraphs.at(j)->keyAxis()->range().size());
            EEGGraphs.at(j)->valueAxis()->axisRect()->axis(QCPAxis::atLeft)->setVisible(0);
            EEGGraphs.at(j)->valueAxis()->axisRect()->axis(QCPAxis::atRight)->grid()->setVisible(1);
            EEGGraphs.at(j)->keyAxis()->axisRect()->axis(QCPAxis::atBottom)->setVisible(1);
            EEGGraphs.at(j)->keyAxis()->axisRect()->axis(QCPAxis::atTop)->setVisible(1);
        }
        EEG_lastkey1 = EEG_lastkey;

        QPixmap bgr;
        bgr.fill(bgr_brush.color());
        ui->EEGPlot->setBackground(bgr,0);
        bgr = ui->EEGPlot->toPixmap(ui->EEGPlot->size().width(),ui->EEGPlot->size().height());
        ui->EEGPlot->setBackground(bgr,0);

        for(int j=0; j<EEGGraphs.count(); j++)
        {
            EEGGraphs.at(j)->valueAxis()->axisRect()->axis(QCPAxis::atLeft)->setVisible(1);
            EEGGraphs.at(j)->valueAxis()->axisRect()->axis(QCPAxis::atLeft)->setTickLabels(1);
            EEGGraphs.at(j)->valueAxis()->axisRect()->axis(QCPAxis::atLeft)->grid()->setVisible(0);
            EEGGraphs.at(j)->valueAxis()->axisRect()->axis(QCPAxis::atRight)->grid()->setVisible(0);
            EEGGraphs.at(j)->keyAxis()->axisRect()->axis(QCPAxis::atBottom)->setVisible(0);
            EEGGraphs.at(j)->keyAxis()->axisRect()->axis(QCPAxis::atTop)->setVisible(0);
        }
    }

    //removing already painted points
    EEG_left=0;
    if(EEGGraphs.at(0)->data()->keys().count()>1)
    {
        EEG_left = EEGGraphs.at(0)->keyAxis()->coordToPixel(EEGGraphs.at(0)->data()->keys().last());//lastPointKey- screendiv*8);
        double leftcoord = EEGGraphs.at(0)->keyAxis()->pixelToCoord(EEG_left-2);
        if(EEGGraphs.at(0)->data()->keys().at(EEGGraphs.at(0)->data()->keys().count()-2)<leftcoord)
            leftcoord=EEGGraphs.at(0)->data()->keys().at(EEGGraphs.at(0)->data()->keys().count()-2);
        for(int j=0; j<EEGGraphs.count(); j++)
        {
            EEGGraphs.at(j)->removeDataBefore(leftcoord);//ui->customPlot->xAxis->pixelToCoord(left-2));
        }
        //left=left;
    }else qDebug() << "no dta";

Drawback is that all data is cleared when plot is resized.

Hey Alex. Do you mind sharing your code? I'm making a same type of program so I would love to see an example! Thanks!