1

I'm trying to figure out how to use QCPLayer to only replot certain items in the plot.

The qcustomplot documentation states this:

If you often need to call a full QCustomPlot::replot only because a non-complex object (e.g. an item) has changed while having relatively static but complex graphs in the plot, consider placing the regularly changing objects onto an own layer and setting its mode (QCPLayer::setMode) to QCPLayer::lmBuffered. This makes QCustomPlot allocate a dedicated paint buffer for this layer, and allows it to be replotted individually with QCPLayer::replot, independent of the other layers containing the potentially complex and slow graphs. See the documentation of the respective methods for details.

Which is what I'm trying to do in the example below:

I am creating a custom qcustomplot by inheriting from QCustomPlot:

QCustomPlot_custom.h

#pragma once
#include "qcustomplot.h"    
#define USING_LAYER false

struct QCPCursor{
   QCPItemLine *hLine;
   QCPItemLine *vLine;
   QCPItemText* cursorText;
};

class QCustomPlot_custom :
   public QCustomPlot
{
   Q_OBJECT    
private slots:
   void mouseMove(QMouseEvent*);

public:
   QCustomPlot_custom(QWidget* parent = NULL);
   ~QCustomPlot_custom(){}
private:
   QCPLayer* cursorLayer;
   QCPCursor cursor;   
   void manageCursor(double x, double y, QPen pen);
public:
   void init(QVector<double> xdata, QVector<double> ydata);
};

This class initializes with some data to plot. It also overloads the mouseMove event to control a custom cursor. USING_LAYER set to true means that the custom cursor is added to it's own layer (cursorLayer).

By setting USING_LAYER to false I get the desired effect as seen below:

Plot with custom cursor

The cursor is displayed by a horizontal and vertical line and the coordinates.

If I have many graphs in the plot and/or a lot of point in each graph, I will see a delay when moving the cursor. (Which is the reason I want to be able to replot only the cursor by setting it in a layer.)

QCustomPlot_custom.cpp

QCustomPlot_custom::QCustomPlot_custom(QWidget* parent)
{
   connect(this, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMove(QMouseEvent*)));   
   QCustomPlot::setInteraction(QCP::iRangeDrag, true);
   QCustomPlot::setInteraction(QCP::iRangeZoom, true);
   if (USING_LAYER){
      this->addLayer("cursorLayer", 0, QCustomPlot::limAbove);
      cursorLayer = new QCPLayer(this, "cursorLayer");
      cursorLayer->setMode(QCPLayer::lmBuffered);
   }
}

void QCustomPlot_custom::init(QVector<double> xdata, QVector<double> ydata)
{   
   this->addGraph();
   this->graph(0)->setData(xdata, ydata);

   QColor colorPen(10, 25, 180, 255);
   QPen pen;
   pen.setWidth(50);
   pen.setColor(colorPen);
   this->graph()->setLineStyle(QCPGraph::lsLine);
   this->graph()->setPen(QPen(colorPen));
   this->xAxis->setLabel("X-axis");
   this->yAxis->setLabel("Y-axis");      
   this->rescaleAxes();
   this->replot();
}

void QCustomPlot_custom::mouseMove(QMouseEvent* event)
{
   //Cursor coordinates:
   double x = this->xAxis->pixelToCoord(event->pos().x());
   double y = this->yAxis->pixelToCoord(event->pos().y());
   manageCursor(x, y, QPen(Qt::DashDotLine));            
   if (USING_LAYER)
      cursorLayer->replot(); 
   else
      this->replot();
}

void QCustomPlot_custom::manageCursor(double x, double y, QPen pen)
{
   if (cursor.hLine)
      this->removeItem(cursor.hLine);
   cursor.hLine = new QCPItemLine(this);   
   cursor.hLine->setPen(pen);
   cursor.hLine->start->setCoords(-QCPRange::maxRange, y);
   cursor.hLine->end->setCoords(QCPRange::maxRange, y);

   if (cursor.vLine)
      this->removeItem(cursor.vLine);
   cursor.vLine = new QCPItemLine(this);   
   cursor.vLine->setPen(pen);
   cursor.vLine->start->setCoords(x, -QCPRange::maxRange);
   cursor.vLine->end->setCoords(x, QCPRange::maxRange);

   //Coordinates as text:   
   if (cursor.cursorText)
      this->removeItem(cursor.cursorText);
   cursor.cursorText = new QCPItemText(this);
   cursor.cursorText->setText(QString("(%1, %2)").arg(x).arg(y));
   cursor.cursorText->position->setCoords(QPointF(x, y));
   QPointF pp = cursor.cursorText->position->pixelPosition() + QPointF(50.0, -15.0);
   cursor.cursorText->position->setPixelPosition(pp);
   cursor.cursorText->setFont(QFont(font().family(), 8));

   //Add to layer:
   if (USING_LAYER){
      cursor.hLine->setLayer(cursorLayer); 
      cursor.vLine->setLayer(cursorLayer); 
      cursor.cursorText->setLayer(cursorLayer);
   }
}

The function that initializes the class member:

void Qt_PlotTest::testPlot(){
   //Create some data and initalize plot:
   QVector<double> yData, xData;
   int imax = 100000;
   for (int i = 0; i < imax; i++){
      double x = double(i) / imax;
      xData.push_back(x);
      yData.push_back(pow(x, 2)*( 1.0 + 0.5*cos(20*x) + 0.1*sin(500*x - 0.1)));
   }
   ui.custom_QWidgetPlot->init(xData, yData);
}

When using the layer method, the cursor doesn't render. I tried understanding the documentation, but it is not clear for me how to correctly use QCPLayers.

How should I do this?

remi
  • 937
  • 3
  • 18
  • 45

2 Answers2

0

After adding layer

this->addLayer("cursorLayer", 0, QCustomPlot::limAbove);

don't call QCPLayer constructor to get layer pointer. Use provided getters with name of the layer or index:

QCPLayer * QCustomPlot::layer ( const QString & name) const

QCPLayer * QCustomPlot::layer ( int index) const

cursorLayer = this->layer("cursorLayer");

Also every Graph and Item is added to currentLayer and in your case it's not cursorLayer it's the main. You need to change current layer

bool QCustomPlot::setCurrentLayer ( const QString & name)

bool QCustomPlot::setCurrentLayer ( QCPLayer * layer)

I.e.:

this->setCurrentLayer("cursorLayer");
this->addGraph();
...
this->setCurrentLayer("main");

Or you can specify layer for each QCPLayerable

bool QCPLayerable::setLayer ( QCPLayer * layer)

bool QCPLayerable::setLayer ( const QString & layerName)

someGraph->setLayer("cursorLayer);
Eligijus Pupeikis
  • 1,115
  • 8
  • 19
  • I tried `cursorLayer = this->layer("cursorLayer");`. I also tried `this->setCurrentLayer("cursorLayer");` before creating `hline`, `vline` and `cursorText` in `QCustomPlot_custom::manageCursor`, as well as setting each item to the layer with f.ex. `hline->setLayer("cursorLayer);`. What happens now is that my cursor is rendered only when I zoom or drag the view (because then `this->replot()` is probably called). If I debug, and dig into `cursorLayer->replot()`, I see that it fails because `bool QCustomPlot::hasInvalidatedPaintBuffers()` returns `true`. – remi Mar 26 '18 at 08:23
  • I'm not sure why would you want to delete and create cursors again after each `mouseMove(QMouseEvent* event)`. Create it once in init and just call `setCoords` in `mouseMove` – Eligijus Pupeikis Mar 26 '18 at 08:28
  • I agree. That method is taken from a different example somewhere. I just wanted to fix this replot issue before changing that. It shouldn't have an effect on this problem though, if I'm always adding it to the layer after recreating the cursor. But maybe, I will have a closer look. – remi Mar 26 '18 at 08:32
0

As @EligijusPupeikis reminded me, I am deleting and re-creating the cursor every time I move it.

I didn't think this would have any effect to my issue, but apparently it is, because reploting a layer which has new items in it requires that the plot has be reploted first (source: will check the qcustomplot doc and add link).

So my code now looks like this:

QCustomPlot_custom::QCustomPlot_custom(QWidget* parent)
{
   connect(this, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMove(QMouseEvent*)));   
   QCustomPlot::setInteraction(QCP::iRangeDrag, true);
   QCustomPlot::setInteraction(QCP::iRangeZoom, true);
}

void QCustomPlot_custom::init(QVector<double> xdata, QVector<double> ydata)
{   
   this->addGraph();
   this->graph(0)->setData(xdata, ydata);

   QColor colorPen(10, 25, 180, 255);
   QPen pen;
   pen.setWidth(50);
   pen.setColor(colorPen);
   this->graph()->setLineStyle(QCPGraph::lsLine);
   this->graph()->setPen(QPen(colorPen));
   this->xAxis->setLabel("X-axis");
   this->yAxis->setLabel("Y-axis");      
   this->rescaleAxes();
   this->replot();

   if (USING_LAYER){
      this->addLayer("cursorLayer", 0, QCustomPlot::limAbove);
      cursorLayer = this->layer("cursorLayer");
      //cursorLayer = new QCPLayer(this, "cursorLayer");
      cursorLayer->setMode(QCPLayer::lmBuffered);
   }

   //Cursor:
   QPen qpen = QPen(Qt::DashDotLine);
   cursor.hLine = new QCPItemLine(this);
   cursor.hLine->setPen(qpen);
   cursor.vLine = new QCPItemLine(this);
   cursor.vLine->setPen(qpen);
   cursor.cursorText = new QCPItemText(this);
   cursor.cursorText->setFont(QFont(font().family(), 8));

   //Add to layer:
   if (USING_LAYER){
      cursor.hLine->setLayer("cursorLayer");  //"cursorLayer"
      cursor.vLine->setLayer("cursorLayer");
      cursor.cursorText->setLayer("cursorLayer");
   }

}

void QCustomPlot_custom::mouseMove(QMouseEvent* event)
{
   //Cursor coordinates:
   double x = this->xAxis->pixelToCoord(event->pos().x());
   double y = this->yAxis->pixelToCoord(event->pos().y());
   manageCursor(x, y);            
   if (USING_LAYER)
      this->layer("cursorLayer")->replot();
   else
      this->replot();
}

void QCustomPlot_custom::manageCursor(double x, double y)
{
   cursor.hLine->start->setCoords(-QCPRange::maxRange, y);
   cursor.hLine->end->setCoords(QCPRange::maxRange, y);

   cursor.vLine->start->setCoords(x, -QCPRange::maxRange);
   cursor.vLine->end->setCoords(x, QCPRange::maxRange);

   cursor.cursorText->setText(QString("(%1, %2)").arg(x).arg(y));
   cursor.cursorText->position->setCoords(QPointF(x, y));
   QPointF pp = cursor.cursorText->position->pixelPosition() + QPointF(50.0, -15.0);      
 cursor.cursorText->position->setPixelPosition(pp);
}

As a test, if I plot 10 000 000 points, and set USING_LAYER to false, I will notice a clear lag on the cursor when moving the mouse. While setting it to true, will result in a smooth cursor movement.

remi
  • 937
  • 3
  • 18
  • 45