QCustomPlot Discussion and Comments

Intractable Horizontal Ruler WidgetReturn to overview

It is possible to have an intractable H/V ruler on the plot?

To create an interactable ruler on the QCustomPlot axis rect, you can use the QCPItemLine class. This class provides a line item that can be placed on the QCustomPlot.
You can create an instance of the QCPItemLine class, set its start and end points to define the ruler's length and position on the QCustomPlot. Also read about the different position types (QCPItemPosition::PositionType), they will come in handy for your purpose.

To make the ruler interactable, you can either work with the selection mechanism, or more convenient for the user, react to the QCustomPlot::mousePress/Move/Release signals, and move the line accordingly (see QCPItemLine::selectTest, to see if the ruler is under the cursor).

Even better would be to subclass QCPItemLine and use its mousePress/Move/Release methods to implement the moving functionality.

Why the QCPItemLine::mousePress/Move/Release and QCPItemLine::selectTest doesn't work properly?

class HLine : public QCPItemLine {
  Q_OBJECT

 public:
  HLine(QCustomPlot *const plot) : QCPItemLine(plot) {
    setPen(QPen(QPen().brush(), 5));
    setSelectable(true);
  }

  void selectEvent(QMouseEvent *event, bool additive, QVariant const &details,
                   bool *selectionStateChanged) override {
    qDebug() << __func__ << " : " << selectionStateChanged;
    QCPItemLine::selectEvent(event, additive, details, selectionStateChanged);
  }
  void mousePressEvent(QMouseEvent *event, QVariant const &details) override {
    qDebug() << __func__ << " : "
             << (parentPlot()->itemAt<HLine>(event->pos()) != nullptr);
    QCPItemLine::mousePressEvent(event, details);
  }
  void mouseMoveEvent(QMouseEvent *event, QPointF const &startPos) override {
    qDebug() << __func__ << " : Moved over";
    QCPItemLine::mouseMoveEvent(event, startPos);
  }
  void mouseReleaseEvent(QMouseEvent *event, QPointF const &startPos) override {
    qDebug() << __func__ << " : Mouse released";
    QCPItemLine::mouseReleaseEvent(event, startPos);
  }
};

Here only QCPItemLine::mousePressEvent works. Everything is fine with the QCustomPlot::mousePress/Move/Release but the problem with directly handling those events from QCustomPlot class is I had to handle things like interactions(QCP::iRangeDrag).

Here is my attempt to use QCPItemLine's mouse events(the HLine class) and QCustomPlot's mouse events(The MyLine class):

#include <QtCore/QSize>
#include <QtGui/QPen>
#include <QtWidgets/QApplication>
//
#include "qcustomplot.h"

class MyLine final : public QCPItemLine {
  Q_OBJECT

 public:
  int x = 2;
  bool is_pressed_ = false;

  MyLine(QCustomPlot *const plot) : QCPItemLine(plot) {
    setPen(QPen(QPen().brush(), 10));
    setHead(QCPLineEnding::esBar);
    setTail(QCPLineEnding::esBar);
    plot->setMouseTracking(true);

    connect(plot, &QCustomPlot::mousePress, [plot, this](QMouseEvent *event) {
      if (auto line = plot->itemAt<MyLine>(event->pos())) {
        is_pressed_ = true;
        x = plot->xAxis->pixelToCoord(event->x());
      }
    });
    connect(plot, &QCustomPlot::mouseMove, [plot, this](QMouseEvent *event) {
      x = plot->xAxis->pixelToCoord(event->x());
      if (is_pressed_) {
        this->QCPItemLine::start->setCoords(x, plot->yAxis->range().lower);
        this->QCPItemLine::end->setCoords(x, plot->yAxis->range().upper);
      } else {
        plot->setCursor(Qt::ArrowCursor);
      }
      if (plot->itemAt<MyLine>(event->pos())) plot->setCursor(Qt::SplitHCursor);
      plot->replot();
    });
    connect(plot, &QCustomPlot::mouseRelease, [plot, this](QMouseEvent *event) {
      if (is_pressed_) {
        is_pressed_ = false;
        plot->setCursor(Qt::ArrowCursor);
        x = plot->xAxis->pixelToCoord(event->x());
      }
    });
  }
};

class HLine : public QCPItemLine {
  Q_OBJECT

 public:
  HLine(QCustomPlot *const plot) : QCPItemLine(plot) {
    setPen(QPen(QPen().brush(), 5));
    setSelectable(true);
  }

  void selectEvent(QMouseEvent *event, bool additive, QVariant const &details,
                   bool *selectionStateChanged) override {
    qDebug() << __func__ << " : " << selectionStateChanged;
    QCPItemLine::selectEvent(event, additive, details, selectionStateChanged);
  }
  void mousePressEvent(QMouseEvent *event, QVariant const &details) override {
    qDebug() << __func__ << " : "
             << (parentPlot()->itemAt<HLine>(event->pos()) != nullptr);
    QCPItemLine::mousePressEvent(event, details);
  }
  void mouseMoveEvent(QMouseEvent *event, QPointF const &startPos) override {
    qDebug() << __func__ << " : Moved over";
    QCPItemLine::mouseMoveEvent(event, startPos);
  }
  void mouseReleaseEvent(QMouseEvent *event, QPointF const &startPos) override {
    qDebug() << __func__ << " : Mouse released";
    QCPItemLine::mouseReleaseEvent(event, startPos);
  }
};

#include "main.moc"

int main(int argc, char **argv) {
  QApplication application(argc, argv);

  auto *const plot = new QCustomPlot;
  new HLine(plot);
  //  new MyLine(plot);
  plot->show();

  return application.exec();
}

Okay, I find out that I shouldn't pass the event to the QCPItemLine because it will ignore it eventually in the QCLayerable class. And QCPItemLine::mouseMoveEvent will be called when a mouse press occurred, so how could I implement the mouse hover for changing the mouse cursor on QCPItemLine?

I think I should stick to the QCustomPlot::mousePress/Move/Release events.