QCustomPlot之鼠标悬浮显示值
参考:https://www.jianshu.com/p/9f2763469030
参考中有一些函数声明了,没实现。我这里补充了部分。
使用到qcustomplot,想在鼠标停在某个点时,显示该点的值。从网上找到例子,加入到工程中。
qcustomplot源码下载:https://www.qcustomplot.com/index.php/download
工程中,点击按钮调出对话框,在对话框中显示折线。
对话框类
DlgPlot1.h
#ifndef DLGPLOT1_H #define DLGPLOT1_H #include <QDialog> #include "qcustomplot.h" #include "CustomPlotTooltip.h" namespace Ui { class DlgPlot1; } class DlgPlot1 : public QDialog { Q_OBJECT public: explicit DlgPlot1(QWidget *parent = nullptr); ~DlgPlot1(); public: private: void setupScatterStyleDemo(QCustomPlot *customPlot); private slots: void on_btnRefresh_clicked(); private: Ui::DlgPlot1 *ui; QCPToolTip* m_pCpTip; }; #endif // DLGPLOT1_H
DlgPlot1.cpp
1 #include "DlgPlot1.h" 2 #include "ui_DlgPlot1.h" 3 #include "CustomPlotTooltip.h" 4 5 DlgPlot1::DlgPlot1(QWidget *parent) : 6 QDialog(parent), 7 ui(new Ui::DlgPlot1) 8 { 9 ui->setupUi(this); 10 QString demoName = "Scatter Style Demo"; 11 this->setWindowTitle(demoName); 12 setupScatterStyleDemo(ui->customPlot); 13 14 m_pCpTip = new QCPToolTip(ui->customPlot); 15 } 16 17 DlgPlot1::~DlgPlot1() 18 { 19 delete ui; 20 } 21 22 void DlgPlot1::on_btnRefresh_clicked() 23 { 24 25 } 26 27 void DlgPlot1::setupScatterStyleDemo(QCustomPlot *customPlot) 28 { 29 customPlot->legend->setVisible(true); 30 customPlot->legend->setFont(QFont("Helvetica", 9)); 31 customPlot->legend->setRowSpacing(-3); 32 QVector<QCPScatterStyle::ScatterShape> shapes; 33 QVector<QColor> colors; 34 colors << Qt::blue; 35 colors << Qt::red; 36 colors << Qt::green; 37 // shapes << QCPScatterStyle::ssCross; 38 // shapes << QCPScatterStyle::ssPlus; 39 // shapes << QCPScatterStyle::ssCircle; 40 shapes << QCPScatterStyle::ssDisc; 41 shapes << QCPScatterStyle::ssDisc; 42 // shapes << QCPScatterStyle::ssSquare; 43 // shapes << QCPScatterStyle::ssDiamond; 44 // shapes << QCPScatterStyle::ssStar; 45 // shapes << QCPScatterStyle::ssTriangle; 46 // shapes << QCPScatterStyle::ssTriangleInverted; 47 // shapes << QCPScatterStyle::ssCrossSquare; 48 // shapes << QCPScatterStyle::ssPlusSquare; 49 // shapes << QCPScatterStyle::ssCrossCircle; 50 // shapes << QCPScatterStyle::ssPlusCircle; 51 // shapes << QCPScatterStyle::ssPeace; 52 // shapes << QCPScatterStyle::ssCustom; 53 // shapes << QCPScatterStyle::ssDot; 54 55 QPen pen; 56 // add graphs with different scatter styles: 57 for (int i=0; i<shapes.size(); ++i) 58 { 59 customPlot->addGraph(); 60 pen.setColor(colors.at(i)); 61 // generate data: 62 QVector<double> x, y; 63 if(i == 0){ 64 x << 2005 << 2006 << 2007 << 2008 << 2009 << 2010 << 2011; 65 y << 2.17 << 3.42 << 4.94 << 10.38 << 15.86 << 29.33 << 52.1; 66 } 67 else if(i == 1){ 68 x << 2005 << 2006 << 2007 << 2008 << 2009 << 2010 << 2011; 69 y << 2 << 9 << 6 << 15 << 89 << 11 << 20; 70 } 71 customPlot->graph()->setData(x, y); 72 customPlot->graph()->rescaleAxes(true); 73 customPlot->graph()->setPen(pen); 74 customPlot->graph()->setName(QCPScatterStyle::staticMetaObject.enumerator(QCPScatterStyle::staticMetaObject.indexOfEnumerator("ScatterShape")).valueToKey(shapes.at(i))); 75 customPlot->graph()->setLineStyle(QCPGraph::lsLine); 76 // set scatter style: 77 if (shapes.at(i) != QCPScatterStyle::ssCustom) 78 { 79 customPlot->graph()->setScatterStyle(QCPScatterStyle(shapes.at(i), 10)); 80 } 81 else 82 { 83 QPainterPath customScatterPath; 84 for (int i=0; i<3; ++i) 85 customScatterPath.cubicTo(qCos(2*M_PI*i/3.0)*9, qSin(2*M_PI*i/3.0)*9, qCos(2*M_PI*(i+0.9)/3.0)*9, qSin(2*M_PI*(i+0.9)/3.0)*9, 0, 0); 86 customPlot->graph()->setScatterStyle(QCPScatterStyle(customScatterPath, QPen(Qt::black, 0), QColor(255, 255, 255, 50), 10)); 87 } 88 } 89 90 // set blank axis lines: 91 customPlot->rescaleAxes(); 92 customPlot->xAxis->setTicks(true); 93 customPlot->yAxis->setTicks(true); 94 customPlot->xAxis->setTickLabels(true); 95 customPlot->yAxis->setTickLabels(true); 96 // make top right axes clones of bottom left axes: 97 customPlot->axisRect()->setupFullAxesBox(); 98 }
DlgPlot1.ui
1 <?xml version="1.0" encoding="UTF-8"?> 2 <ui version="4.0"> 3 <class>DlgPlot1</class> 4 <widget class="QDialog" name="DlgPlot1"> 5 <property name="geometry"> 6 <rect> 7 <x>0</x> 8 <y>0</y> 9 <width>400</width> 10 <height>300</height> 11 </rect> 12 </property> 13 <property name="windowTitle"> 14 <string>Dialog</string> 15 </property> 16 <layout class="QGridLayout" name="gridLayout"> 17 <item row="0" column="0" colspan="2"> 18 <widget class="QCustomPlot" name="customPlot" native="true"/> 19 </item> 20 <item row="1" column="0"> 21 <spacer name="horizontalSpacer"> 22 <property name="orientation"> 23 <enum>Qt::Horizontal</enum> 24 </property> 25 <property name="sizeHint" stdset="0"> 26 <size> 27 <width>293</width> 28 <height>20</height> 29 </size> 30 </property> 31 </spacer> 32 </item> 33 <item row="1" column="1"> 34 <widget class="QPushButton" name="btnRefresh"> 35 <property name="text"> 36 <string>刷 新</string> 37 </property> 38 </widget> 39 </item> 40 </layout> 41 </widget> 42 <customwidgets> 43 <customwidget> 44 <class>QCustomPlot</class> 45 <extends>QWidget</extends> 46 <header location="global">qcustomplot.h</header> 47 <container>1</container> 48 </customwidget> 49 </customwidgets> 50 <resources/> 51 <connections/> 52 </ui>
CustomPlotTooltip.h
1 #ifndef CUSTOMPLOTTOOLTIP_H 2 #define CUSTOMPLOTTOOLTIP_H 3 #include "qcustomplot.h" 4 5 class QCPToolTip : public QCPAbstractItem 6 { 7 Q_OBJECT 8 public: 9 explicit QCPToolTip(QCustomPlot *parentPlot); 10 11 void setText(const QString &text); 12 void setFont(const QFont &font); 13 void setTextColor(const QColor &color); 14 void setBorderPen(const QPen &pen); 15 void setBrush(const QBrush &brush); 16 void setRadius(double xRadius, double yRadius, Qt::SizeMode mode = Qt::AbsoluteSize); 17 void setOffset(double xOffset, double yOffset); 18 void setPadding(const QMargins &paddings); 19 20 Q_SLOT void handleTriggerEvent(QMouseEvent *event); 21 void updatePosition(const QPointF &newPos, bool replot = false); 22 23 void update(); 24 25 virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const Q_DECL_OVERRIDE; 26 27 QCPItemPosition * const position; 28 protected: 29 bool mPlotReplot; // 表明是由QCustomPlot刷新的,需要更新位置 30 QString mText; 31 Qt::Alignment mTextAlignment; 32 QFont mFont; 33 QColor mTextColor; 34 QPen mBorderPen; 35 QBrush mBrush; 36 37 QPointF mRadius; 38 Qt::SizeMode mSizeMode; 39 40 QPointF mOffset; // 偏移鼠标的距离 41 QMargins mPadding; 42 43 QCPGraph *mHighlightGraph; 44 QPointF mGraphDataPos; 45 46 virtual void draw(QCPPainter *painter) Q_DECL_OVERRIDE; 47 virtual void drawGraphScatterPlot(QCPPainter *painter, QCPGraph *graph, const QPointF &pos); 48 49 int pickClosest(double target, const QVector<double> &vector); 50 }; 51 #endif // CUSTOMPLOTTOOLTIP_H
CustomPlotTooltip.cpp
1 #include "CustomPlotTooltip.h" 2 #include <QToolTip> 3 #include <QCursor> 4 5 QCPToolTip::QCPToolTip(QCustomPlot *parentPlot) 6 : QCPAbstractItem(parentPlot), 7 position(createPosition(QLatin1String("position"))), 8 mPlotReplot(true), 9 mTextAlignment(Qt::AlignLeft | Qt::AlignVCenter), 10 mRadius(6, 6), 11 mSizeMode(Qt::AbsoluteSize), 12 mHighlightGraph(nullptr) 13 { 14 position->setType(QCPItemPosition::ptAbsolute); 15 setSelectable(false); 16 setLayer("overlay"); 17 18 setBorderPen(Qt::NoPen); 19 setBrush(QColor(87, 98, 93, 180)); 20 setTextColor(Qt::white); 21 setOffset(10, 0); 22 setPadding(QMargins(6, 6, 6, 6)); 23 connect(mParentPlot, SIGNAL(mouseMove(QMouseEvent *)), this, SLOT(handleTriggerEvent(QMouseEvent *))); 24 } 25 26 void QCPToolTip::setText(const QString &text) 27 { 28 mText = text; 29 // QToolTip::showText(mParentPlot->cursor().pos(), text, mParentPlot); 30 } 31 32 void QCPToolTip::setFont(const QFont &font) 33 { 34 mFont = font; 35 } 36 37 void QCPToolTip::setTextColor(const QColor &color) 38 { 39 mTextColor = color; 40 } 41 42 void QCPToolTip::setBorderPen(const QPen &pen) 43 { 44 mBorderPen = pen; 45 } 46 47 void QCPToolTip::setBrush(const QBrush &brush) 48 { 49 mBrush = brush; 50 } 51 52 void QCPToolTip::setRadius(double xRadius, double yRadius, Qt::SizeMode mode) 53 { 54 mRadius.setX(xRadius); 55 mRadius.setY(yRadius); 56 mSizeMode = mode; 57 } 58 59 void QCPToolTip::setOffset(double xOffset, double yOffset) 60 { 61 mOffset.setX(xOffset); 62 mOffset.setY(yOffset); 63 } 64 65 void QCPToolTip::setPadding(const QMargins &paddings) 66 { 67 mPadding = paddings; 68 } 69 70 void QCPToolTip::handleTriggerEvent(QMouseEvent *event) 71 { 72 updatePosition(event->pos(), true); // true 表示需要单独刷新,将调用update函数 73 } 74 75 void QCPToolTip::update() 76 { 77 mPlotReplot = false; // 表明单独刷新 78 layer()->replot(); 79 mPlotReplot = true; // 单独刷新完毕 80 } 81 82 // 不需要鼠标点击测试,因为ToolTip是跟随鼠标的,鼠标点击不到 83 double QCPToolTip::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const 84 { 85 Q_UNUSED(pos) 86 Q_UNUSED(onlySelectable) 87 Q_UNUSED(details) 88 return -1; 89 } 90 91 int QCPToolTip::pickClosest(double target, const QVector<double> &vector) 92 { 93 if (vector.size() < 2) 94 return 0; 95 96 // 查找第一个大于或等于target的位置 97 auto it = std::lower_bound(vector.constBegin(), vector.constEnd(), target); 98 99 if (it == vector.constEnd()) return vector.size() - 1; 100 else if (it == vector.constBegin()) return 0; 101 else return target - *(it - 1) < *it - target ? (it - vector.constBegin() - 1): (it - vector.constBegin()); 102 } 103 104 void QCPToolTip::updatePosition(const QPointF &newPos, bool replot) 105 { 106 mHighlightGraph = nullptr; 107 double tolerance = mParentPlot->selectionTolerance(); 108 109 for (int i = mParentPlot->graphCount() - 1; i >= 0; --i) { 110 QCPGraph *graph = mParentPlot->graph(i); 111 if (!graph->realVisibility() || graph->scatterStyle().isNone()) // graph不可见或者scatter style 为空的时候,不显示ToolTip 112 continue; 113 114 double limitDistance = tolerance; // limitDistance 用于选择的范围 115 double penWidth = graph->pen().widthF(); 116 QCPScatterStyle scatterStyle = graph->scatterStyle(); 117 118 limitDistance = qMax(scatterStyle.size(), tolerance); 119 penWidth = scatterStyle.isPenDefined() ? scatterStyle.pen().widthF() : penWidth; 120 121 QVariant details; 122 double currentDistance = graph->selectTest(newPos, false, &details); // details会返回最接近的一个数据点,selectTest是不精确的,所以后面还要判断 123 124 QCPDataSelection selection = details.value<QCPDataSelection>(); 125 if (currentDistance >= 0 && currentDistance < limitDistance + penWidth && !selection.isEmpty()) { 126 // 取出当前key和value值,并且转换为像素位置 127 double key = graph->dataMainKey(selection.dataRange().begin()); 128 double value = graph->dataMainValue(selection.dataRange().begin()); 129 QPointF pos = graph->coordsToPixels(key, value); 130 131 QRectF rect(pos.x() - limitDistance * 0.5, pos.y() - limitDistance * 0.5, limitDistance, limitDistance); 132 rect = rect.adjusted(-penWidth, -penWidth, penWidth, penWidth); 133 134 if (rect.contains(newPos)) { // 通过矩形判断,鼠标位置是否在数据点上 135 // // 解开以下注释,可以使得我们的文字跟轴标签的文字是一样的(但跟轴标签实际的显示效果可能是不一样的,这里要注意,例如对于科学计数法,轴可能会使用美化),同时要注意当轴标签不显示的时候tickVectorLabels返回的是空的,所以我们要做一下判断 136 // // 注意这里的方式是不精确的,适用于文字轴这种类型的 137 // int keyIndex = pickClosest(key, graph->keyAxis()->tickVector()); 138 // setText(QString("%1:%2").arg(graph->keyAxis()->tickVectorLabels().at(keyIndex), 139 // QString::number(value))); 140 setText(QString("%1:%2").arg(QString::number(key), QString::number(value))); 141 mHighlightGraph = graph; 142 mGraphDataPos = pos; 143 144 mParentPlot->setCursor(Qt::PointingHandCursor); 145 position->setPixelPosition(newPos); // 更新位置 146 setVisible(true); 147 148 if (replot) update(); 149 break; 150 } 151 } 152 } 153 154 if (!mHighlightGraph && visible()) { 155 mParentPlot->setCursor(Qt::ArrowCursor); 156 setVisible(false); 157 if (replot) update(); 158 } 159 } 160 161 void QCPToolTip::draw(QCPPainter *painter) 162 { 163 if (mPlotReplot) { // 当前是由QCustomPlot的replot函数刷新的,所以要更新位置 164 updatePosition(position->pixelPosition(), false); // 传入false表明不刷新 165 if (!visible()) return; // 由于位置更新之后,ToolTip可能会隐藏掉了,所以此处直接返回 166 } 167 168 drawGraphScatterPlot(painter, mHighlightGraph, mGraphDataPos); 169 170 QPointF pos = position->pixelPosition() + mOffset; 171 painter->translate(pos); // 移动painter的绘制原点位置 172 173 QFontMetrics fontMetrics(mFont); 174 QRect textRect = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | mTextAlignment, mText); 175 textRect.moveTopLeft(QPoint(mPadding.left(), mPadding.top())); 176 177 QRect textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom()); 178 textBoxRect.moveTopLeft(QPoint()); 179 180 // 限制ToolTip不超过QCustomPlot的范围 181 if (pos.x() + textBoxRect.width() >= mParentPlot->viewport().right()) 182 painter->translate(-mOffset.x() * 2 - textBoxRect.width(), 0); 183 if (pos.y() + textBoxRect.height() * 2 >= mParentPlot->viewport().bottom()) 184 painter->translate(0, -mOffset.y() * 2 - textBoxRect.height()); 185 186 // 绘制背景和边框 187 if ((mBrush != Qt::NoBrush && mBrush.color().alpha() != 0) || 188 (mBorderPen != Qt::NoPen && mBorderPen.color().alpha() != 0)) { 189 double clipPad = mBorderPen.widthF(); 190 QRect boundingRect = textBoxRect.adjusted(-clipPad, -clipPad, clipPad, clipPad); 191 192 painter->setPen(mBorderPen); 193 painter->setBrush(mBrush); 194 painter->drawRoundedRect(boundingRect, mRadius.x(), mRadius.y(), mSizeMode); 195 } 196 197 // 绘制文字 198 painter->setFont(mFont); 199 painter->setPen(mTextColor); 200 painter->setBrush(Qt::NoBrush); 201 painter->drawText(textRect, Qt::TextDontClip | mTextAlignment, mText); 202 } 203 204 void QCPToolTip::drawGraphScatterPlot(QCPPainter *painter, QCPGraph *graph, const QPointF &pos) 205 { 206 if (!graph) return; 207 208 QCPScatterStyle style = graph->scatterStyle(); 209 if (style.isNone()) return; 210 211 if (graph->selectionDecorator()) // 如果有select decorator,则使用修饰器的风格 212 style = graph->selectionDecorator()->getFinalScatterStyle(style); 213 214 style.applyTo(painter, graph->pen()); 215 style.setSize(style.size() * 1.2); // 放大一点 216 style.drawShape(painter, pos); 217 }