c++qtqcustomplot

QCustomPlot Interact with a single point on a graph


I am using QCustomPlot and have a multiple graphs plotted on the screen, i want to be able to click and point and then be able to get the data or coordinates etc of the point that I clicked on, I know this is possible for the entire plot itself using QCP::iSelectPlottablesbut is this possible for just an individual point or has anyone found a work around to make this possible.


Solution

  • There is no simple way to do that. At least there is no such functionality in QCustomPlot.

    But you can create class representing single point (derived from QCPItemEllipse, for example) and move it with mouse.

    I've got similiar functionality in my (not released yet) software, so look and learn... It also can move with shift-modifier (changing only one coordinate of initial position). Plus it changes cursor when it moves to item (and border of item it moves to).

    plotpoint.h

    class PlotPoint : public QCPItemEllipse
    {
        Q_OBJECT
    public:
        explicit PlotPoint(QCustomPlot *parentPlot, int halfSize = 5);
    
        QPointF pos() const;
        const QColor &color() const;
        void setColor(const QColor &color);
        void startMoving(const QPointF &mousePos, bool shiftIsPressed);
    
    public slots:
        void setVisible(bool on);
    
    signals:
        void activated(); ///< emitted on mouse over
        void disactivated(); ///< emitted when cursor leave us
    
        void moved(const QPointF &pos);
        void stoppedMoving();
    
    public slots:
        void move(double x, double y, bool signalNeeded = true);
        void movePx(double x, double y);
        void setActive(bool isActive);
    
    private slots:
        void onMouseMove(QMouseEvent *event);
        void stopMoving();
        void moveToWantedPos();
        void onShiftStateChanged(bool shiftPressed);
    
    private:
        QCPItemTracer *mCenterTracer;
        QPointF mGripDelta;
        QPointF mInitialPos;
        bool mIsChangingOnlyOneCoordinate;
        QList<QCPAbstractItem *> mHelperItems;
        QPointF mLastWantedPos;
        QTimer *mMoveTimer;
        QPointF mCurWantedPosPx;
    };
    

    plotpoint.cpp

    PlotPoint::PlotPoint(QCustomPlot *parentPlot, int halfSize)
        : QCPItemEllipse(parentPlot)
        , mCenterTracer(new QCPItemTracer(parentPlot))
        , mGripDelta()
        , mInitialPos()
        , mLastWantedPos()
        , mMoveTimer(new QTimer(this))
        , mCurWantedPosPx()
    {
        mCenterTracer->setStyle(QCPItemTracer::tsNone);
    
        topLeft->setParentAnchor(mCenterTracer->position);
        bottomRight->setParentAnchor(mCenterTracer->position);
        topLeft->setType(QCPItemPosition::ptAbsolute);
        bottomRight->setType(QCPItemPosition::ptAbsolute);
    
        topLeft->setCoords(-halfSize, -halfSize);
        bottomRight->setCoords(halfSize, halfSize);
    
        setSelectable(true); // plot moves only selectable points, see Plot::mouseMoveEvent
        setColor(QColor(qrand()%256, qrand()%256, qrand()%256, 100));
        setPen(QPen(Qt::black));
        setSelectedPen(QPen(Qt::black, 3));
    
        mMoveTimer->setInterval(25); // 40 FPS
        connect(mMoveTimer, SIGNAL(timeout()), this, SLOT(moveToWantedPos()));
    }
    
    QPointF PlotPoint::pos() const
    {
        return mCenterTracer->position->coords();
    }
    
    const QColor &PlotPoint::color() const
    {
        return brush().color();
    }
    
    void PlotPoint::setColor(const QColor& color)
    {
        setBrush(color);
        setSelectedBrush(color);
    }
    
    void PlotPoint::startMoving(const QPointF &mousePos, bool shiftIsPressed)
    {
        mGripDelta.setX(parentPlot()->xAxis->coordToPixel(mCenterTracer->position->key()) - mousePos.x());
        mGripDelta.setY(parentPlot()->yAxis->coordToPixel(mCenterTracer->position->value()) - mousePos.y());
    
        mInitialPos = pos();
        mLastWantedPos = mInitialPos;
        mCurWantedPosPx = QPointF();
        mIsChangingOnlyOneCoordinate = shiftIsPressed;
    
        mMoveTimer->start();
    
        QCPItemStraightLine *horizontal = new QCPItemStraightLine(parentPlot());
        horizontal->setAntialiased(false);
        horizontal->point1->setCoords(mInitialPos.x(), mInitialPos.y());
        horizontal->point2->setCoords(mInitialPos.x() + 1, mInitialPos.y());
        parentPlot()->addItem(horizontal);
    
        QCPItemStraightLine *vertical = new QCPItemStraightLine(parentPlot());
        vertical->setAntialiased(false);
        vertical->point1->setCoords(mInitialPos.x(), mInitialPos.y());
        vertical->point2->setCoords(mInitialPos.x(), mInitialPos.y() + 1);
        parentPlot()->addItem(vertical);
    
        static const QPen linesPen(Qt::darkGray, 0, Qt::DashLine);
        horizontal->setPen(linesPen);
        vertical->setPen(linesPen);
    
        mHelperItems << vertical << horizontal;
    
        if (!mIsChangingOnlyOneCoordinate) {
            vertical->setVisible(false);
            horizontal->setVisible(false);
        }
    
        connect(parentPlot(), SIGNAL(mouseMove(QMouseEvent*)),
                this, SLOT(onMouseMove(QMouseEvent*)));
    
        connect(parentPlot(), SIGNAL(mouseRelease(QMouseEvent*)),
                this, SLOT(stopMoving()));
    
        connect(parentPlot(), SIGNAL(shiftStateChanged(bool)),
                this, SLOT(onShiftStateChanged(bool)));
    
        parentPlot()->grabKeyboard();
        QApplication::setOverrideCursor(Qt::ClosedHandCursor);
    }
    
    void PlotPoint::setVisible(bool on)
    {
        setSelectable(on);  // we are movable only when visible
        QCPItemEllipse::setVisible(on);
    }
    
    void PlotPoint::stopMoving()
    {
        disconnect(parentPlot(), SIGNAL(mouseMove(QMouseEvent*)),
                this, SLOT(onMouseMove(QMouseEvent*)));
    
        disconnect(parentPlot(), SIGNAL(mouseRelease(QMouseEvent*)),
                this, SLOT(stopMoving()));
    
        disconnect(parentPlot(), SIGNAL(shiftStateChanged(bool)),
                this, SLOT(onShiftStateChanged(bool)));
    
        mMoveTimer->stop();
        moveToWantedPos();
    
        if (!mHelperItems.isEmpty()) {
            while (!mHelperItems.isEmpty()) {
                QCPAbstractItem *item = mHelperItems.takeFirst();
                mParentPlot->removeItem(item);
            }
    
            mParentPlot->replot();
        }
    
        parentPlot()->releaseKeyboard();
        QApplication::restoreOverrideCursor();
    
        emit stoppedMoving();
    }
    
    void PlotPoint::move(double x, double y, bool signalNeeded)
    {
        mLastWantedPos.setX(x);
        mLastWantedPos.setY(y);
        if (mIsChangingOnlyOneCoordinate) {
            double x1 = parentPlot()->xAxis->coordToPixel(x);
            double x2 = parentPlot()->xAxis->coordToPixel(mInitialPos.x());
            double y1 = parentPlot()->yAxis->coordToPixel(y);
            double y2 = parentPlot()->yAxis->coordToPixel(mInitialPos.y());
            if (qAbs(x1 - x2) < qAbs(y1 - y2)) {
                x = mInitialPos.x();
            } else {
                y = mInitialPos.y();
            }
        }
    
        mCenterTracer->position->setCoords(x, y);
    
        parentPlot()->replot();
    
        if(signalNeeded) {
            emit moved(QPointF(x, y));
        }
    }
    
    void PlotPoint::movePx(double x, double y)
    {
        move(parentPlot()->xAxis->pixelToCoord(x),
            parentPlot()->yAxis->pixelToCoord(y));
    }
    
    void PlotPoint::setActive(bool isActive)
    {
        setSelected(isActive);
        emit (isActive ? activated() : disactivated());
    }
    
    void PlotPoint::onMouseMove(QMouseEvent *event)
    {
        mCurWantedPosPx = QPointF(event->localPos().x() + mGripDelta.x(),
                                  event->localPos().y() + mGripDelta.y());
    }
    
    void PlotPoint::moveToWantedPos()
    {
        if (!mCurWantedPosPx.isNull()) {
            movePx(mCurWantedPosPx.x(), mCurWantedPosPx.y());
            mCurWantedPosPx = QPointF();
        }
    }
    
    void PlotPoint::onShiftStateChanged(bool shiftPressed)
    {
        if (shiftPressed != mIsChangingOnlyOneCoordinate) {
            mIsChangingOnlyOneCoordinate = shiftPressed;
            foreach (QCPAbstractItem *item, mHelperItems) {
                item->setVisible(shiftPressed);
            }
            move(mLastWantedPos.x(), mLastWantedPos.y());
        }
    }
    

    (part of) plot.cpp

    void Plot::mousePressEvent(QMouseEvent *event)
    {
        if (event->button() == Qt::LeftButton && mPointUnderCursor) {
            mPointUnderCursor->startMoving(event->localPos(),
                                           event->modifiers().testFlag(Qt::ShiftModifier));
            return;
        }
    
        QCustomPlot::mousePressEvent(event);
    }
    
    void Plot::mouseMoveEvent(QMouseEvent *event)
    {
        QCustomPlot::mouseMoveEvent(event);
        if (event->buttons() == Qt::NoButton) {
            PlotPoint *plotPoint = qobject_cast<PlotPoint*>(itemAt(event->localPos(), true));
            if (plotPoint != mPointUnderCursor) {
                if (mPointUnderCursor == NULL) {
                    // cursor moved from empty space to item
                    plotPoint->setActive(true);
                    setCursor(Qt::OpenHandCursor);
                } else if (plotPoint == NULL) {
                    // cursor move from item to empty space
                    mPointUnderCursor->setActive(false);
                    unsetCursor();
                } else {
                    // cursor moved from item to item
                    mPointUnderCursor->setActive(false);
                    plotPoint->setActive(true);
                }
                mPointUnderCursor = plotPoint;
                replot();
            }
        }
    }
    
    void Plot::keyPressEvent(QKeyEvent *event)
    {
        if (event->key() == Qt::Key_Shift) {
            emit shiftStateChanged(true);
        }
        QCustomPlot::keyPressEvent(event);
    }
    
    void Plot::keyReleaseEvent(QKeyEvent *event)
    {
        if (event->key() == Qt::Key_Shift) {
            emit shiftStateChanged(false);
        }
        QCustomPlot::keyReleaseEvent(event);
    }
    

    Sorry for almost no comments in code. I'm just too lazy for Russian to English translation.

    I hope you'll get everything anyways.