How add crosshair QtChart (with signed values)?
-
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).
What is at the moment:- 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).
- 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++.
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
-
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:
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!