Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. How add crosshair QtChart (with signed values)?
Forum Updated to NodeBB v4.3 + New Features

How add crosshair QtChart (with signed values)?

Scheduled Pinned Locked Moved Unsolved General and Desktop
3 Posts 2 Posters 1.9k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • R Offline
    R Offline
    rysich
    wrote on last edited by
    #1

    Task: There is a chart (QtChart). Code:

    from random import uniform
    import sys
    
    from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
    from PyQt5.QtChart import QChart, QChartView, QLineSeries
    
    class Window(QMainWindow):
        def __init__(self):
            super().__init__()
            self.setGeometry(100, 100, 680, 500)
    
            series = QLineSeries()
            for i in range(100):
                series.append(i, uniform(0, 10))
    
            chart = QChart()
            chart.addSeries(series)
            chart.createDefaultAxes()
            chartview = QChartView(chart)
    
            central_widget = QWidget()
            self.setCentralWidget(central_widget)
            lay = QVBoxLayout(central_widget)
            lay.addWidget(chartview)
            self.setMouseTracking(True)
    
        def mouseMoveEvent(self, event):
            mouse_x = event.x()
            mouse_y = event.y()
    
    if __name__ == "__main__":
        App = QApplication(sys.argv)
        window = Window()
        window.show()
        sys.exit(App.exec_())
    

    It is necessary: Add crosshair with signed values to the chart (QtChart).
    fb59013c-6680-42fe-bcba-28c5caf7b2b1-image.png
    What is at the moment:

    1. Thanks to the example https://stackoverflow.com/questions/41688668/how-to-return-mouse-coordinates-in-realtime, there is an understanding of how to get the mouse coordinates (code updated).
    2. Unfortunately, we didn’t succeed in making any progress. An example was found in the chart documentation (QT) with similar functionality https://doc.qt.io/qt-5/qtcharts-callout-example.html (not quite what you need), but the main problem is that the code is written in C++.
      893bc5e9-85ae-4119-af8c-5b5d88f4b680-image.png

    Below is the code in C++ (if it helps):

    #include "callout.h"
    #include <QtGui/QPainter>
    #include <QtGui/QFontMetrics>
    #include <QtWidgets/QGraphicsSceneMouseEvent>
    #include <QtGui/QMouseEvent>
    #include <QtCharts/QChart>
    
    Callout::Callout(QChart *chart):
        QGraphicsItem(chart),
        m_chart(chart)
    {
    }
    
    QRectF Callout::boundingRect() const
    {
        QPointF anchor = mapFromParent(m_chart->mapToPosition(m_anchor));
        QRectF rect;
        rect.setLeft(qMin(m_rect.left()+20, anchor.x()));
        rect.setRight(qMax(m_rect.right(), anchor.x()));
        rect.setTop(qMin(m_rect.top(), anchor.y()));
        rect.setBottom(qMax(m_rect.bottom(), anchor.y()));
        return rect;
    }
    
    void Callout::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        Q_UNUSED(option)
        Q_UNUSED(widget)
        QPainterPath path;
        path.addRoundedRect(m_rect, 0, 0);
    
        QPointF anchor = mapFromParent(m_chart->mapToPosition(m_anchor));
        if (!m_rect.contains(anchor)) {
            QPointF point1, point2;
    
            // establish the position of the anchor point in relation to m_rect
            bool above = anchor.y() <= m_rect.top();
            bool aboveCenter = anchor.y() > m_rect.top() && anchor.y() <= m_rect.center().y();
            bool belowCenter = anchor.y() > m_rect.center().y() && anchor.y() <= m_rect.bottom();
            bool below = anchor.y() > m_rect.bottom();
    
            bool onLeft = anchor.x() <= m_rect.left();
            bool leftOfCenter = anchor.x() > m_rect.left() && anchor.x() <= m_rect.center().x();
            bool rightOfCenter = anchor.x() > m_rect.center().x() && anchor.x() <= m_rect.right();
            bool onRight = anchor.x() > m_rect.right();
    
            // get the nearest m_rect corner.
            qreal x = (onRight + rightOfCenter) * m_rect.width();
            qreal y = (below + belowCenter) * m_rect.height();
            bool cornerCase = (above && onLeft) || (above && onRight) || (below && onLeft) || (below && onRight);
            bool vertical = qAbs(anchor.x() - x) > qAbs(anchor.y() - y);
    
            qreal x1 = x + leftOfCenter * 10 - rightOfCenter * 20 + cornerCase * !vertical * (onLeft * 10 - onRight * 20);
            qreal y1 = y + aboveCenter * 10 - belowCenter * 20 + cornerCase * vertical * (above * 10 - below * 20);;
            point1.setX(x1);
            point1.setY(y1);
    
            qreal x2 = x + leftOfCenter * 20 - rightOfCenter * 10 + cornerCase * !vertical * (onLeft * 20 - onRight * 10);;
            qreal y2 = y + aboveCenter * 20 - belowCenter * 10 + cornerCase * vertical * (above * 20 - below * 10);;
            point2.setX(x2);
            point2.setY(y2);
    
            path.moveTo(point1);
            path.lineTo(anchor);
            path.lineTo(point2);
            path = path.simplified();
        }
        painter->setBrush(QColor(255, 255, 255));
        painter->drawPath(path);
        painter->drawText(m_textRect, m_text);
    }
    
    void Callout::mousePressEvent(QGraphicsSceneMouseEvent *event)
    {
        event->setAccepted(true);
    }
    
    void Callout::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
    {
        if (event->buttons() & Qt::LeftButton){
            setPos(mapToParent(event->pos() - event->buttonDownPos(Qt::LeftButton)));
            event->setAccepted(true);
        } else {
            event->setAccepted(false);
        }
    }
    
    void Callout::setText(const QString &text)
    {
        m_text = text;
        QFontMetrics metrics(m_font);
        m_textRect = metrics.boundingRect(QRect(0, 0, 250, 250), Qt::AlignLeft, m_text);
        m_textRect.translate(5, 5);
        prepareGeometryChange();
        m_rect = m_textRect.adjusted(-5, -5, 5, 5);
    }
    
    void Callout::setAnchor(QPointF point)
    {
        m_anchor = point;
    }
    
    void Callout::updateGeometry()
    {
        prepareGeometryChange();
        setPos(m_chart->mapToPosition(m_anchor) + QPoint(10, -50));
    }
    python pyqt5 qtcharts pyqtchart
    share  edit  delete  flag
    
    1 Reply Last reply
    0
    • R Offline
      R Offline
      rysich
      wrote on last edited by
      #2

      Maybe in QtCharts there is no such functionality?

      1 Reply Last reply
      0
      • G Offline
        G Offline
        GiancarloV
        wrote on last edited by
        #3

        I recently tackled this problem for a project I'm working on. The solution was small enough that I don't mind sharing.

        See the results below:
        a559b028-0656-48a5-9d43-083a8fb031e7-image.png

        I know this isn't styled exactly as your mockup, but hopefully it is enough to get you started. You can style as desired afterwards. My prototype was implemented right in the callout example. It's possible you won't need the QChartGlobal include or NAMESPACE macros in your adaptation.

        crosshairs.h

        #ifndef CROSSHAIRS_H
        #define CROSSHAIRS_H
        
        #include <QtCharts/QChartGlobal>
        #include <QtWidgets/QGraphicsItem>
        
        QT_CHARTS_BEGIN_NAMESPACE
        class QChart;
        QT_CHARTS_END_NAMESPACE
        
        QT_CHARTS_USE_NAMESPACE
        
        class Crosshairs
        {
        public:
            Crosshairs(QChart *chart);
            void updatePosition(QPointF position);
        
        private:
            QGraphicsLineItem *m_xLine, *m_yLine;
            QGraphicsTextItem *m_xText, *m_yText;
            QChart *m_chart;
        };
        
        #endif // CROSSHAIRS_H
        

        crosshairs.cpp

        #include "crosshairs.h"
        #include <QtCharts/QChart>
        #include <QtGui/QPainter>
        #include <QtGui/QCursor>
        #include <QtGui/QTextDocument>
        
        QT_CHARTS_USE_NAMESPACE
        
        Crosshairs::Crosshairs(QChart *chart) :
            m_xLine(new QGraphicsLineItem(chart)),
            m_yLine(new QGraphicsLineItem(chart)),
            m_xText(new QGraphicsTextItem(chart)),
            m_yText(new QGraphicsTextItem(chart)),
            m_chart(chart)
        {
            m_xLine->setPen(QPen(Qt::red, 0.0));
            m_yLine->setPen(QPen(Qt::red, 0.0));
            m_xText->setZValue(11);
            m_yText->setZValue(11);
            m_xText->document()->setDocumentMargin(0);
            m_yText->document()->setDocumentMargin(0);
            m_xText->setDefaultTextColor(Qt::white);
            m_yText->setDefaultTextColor(Qt::white);
        }
        
        void Crosshairs::updatePosition(QPointF position)
        {
            QLineF xLine(position.x(), m_chart->plotArea().top(),
                         position.x(), m_chart->plotArea().bottom());
            QLineF yLine(m_chart->plotArea().left(), position.y(),
                         m_chart->plotArea().right(), position.y());
            m_xLine->setLine(xLine);
            m_yLine->setLine(yLine);
        
            QString xText = QString("%1").arg(m_chart->mapToValue(position).x()),
                    yText = QString("%1").arg(m_chart->mapToValue(position).y());
            m_xText->setHtml(QString("<div style='background-color: #ff0000;'>") + xText + "</div>");
            m_yText->setHtml(QString("<div style='background-color: #ff0000;'>") + yText + "</div>");
            m_xText->setPos(position.x() - m_xText->boundingRect().width() / 2.0, m_chart->plotArea().bottom());
            m_yText->setPos(m_chart->plotArea().right(), position.y() - m_yText->boundingRect().height() / 2.0);
        
            if (!m_chart->plotArea().contains(position))
            {
                m_xLine->hide();
                m_xText->hide();
                m_yLine->hide();
                m_yText->hide();
            }
            else
            {
                m_xLine->show();
                m_xText->show();
                m_yLine->show();
                m_yText->show();
            }
        }
        

        Declare a member variable in view.h and instantiate it in the constructor.

            m_crosshairs = new Crosshairs(m_chart);
        

        Then, update the crosshair's position from View::mouseMoveEvent

            m_crosshairs->updatePosition(event->pos());
        

        Voila. Hope this helps!

        1 Reply Last reply
        0

        • Login

        • Login or register to search.
        • First post
          Last post
        0
        • Categories
        • Recent
        • Tags
        • Popular
        • Users
        • Groups
        • Search
        • Get Qt Extensions
        • Unsolved