Qt QCustomPlot 鼠标悬浮提示

使用QCustomPlot绘图时,相信大多数童鞋们都会有类似的诉求:希望鼠标移动到节点时,可以显示该节点的数据。这里转载了一篇关于 鼠标悬浮提示 的一篇文章,并对该文章涉及的代码经过了整理,经实践证明是可行的。

鼠标悬浮提示效果图

QCPToolTip

头文件


 QCPToolTip

class QCPToolTip : public QCPAbstractItem
{
    Q_OBJECT
public:
    explicit QCPToolTip(QCustomPlot *parentPlot);

    void setText(const QString &text);
    void setFont(const QFont &font);
    void setTextColor(const QColor &color);
    void setBorderPen(const QPen &pen);
    void setBrush(const QBrush &brush);
    void setRadius(double xRadius, double yRadius, Qt::SizeMode mode = Qt::AbsoluteSize);
    void setOffset(double xOffset, double yOffset);
    void setPadding(const QMargins &paddings);

    Q_SLOT void handleTriggerEvent(QMouseEvent *event);
    void updatePosition(const QPointF &newPos, bool replot = false);

    void update();
    virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const Q_DECL_OVERRIDE;

    QCPItemPosition * const position;

protected:
    bool mPlotReplot;    // 表明是由QCustomPlot刷新的,需要更新位置
    QString mText;
    Qt::Alignment mTextAlignment;
    QFont mFont;
    QColor mTextColor;
    QPen mBorderPen;
    QBrush mBrush;

    QPointF mRadius;
    Qt::SizeMode mSizeMode;

    QPointF mOffset;     // 偏移鼠标的距离
    QMargins mPadding;

    QCPGraph *mHighlightGraph;
    QPointF mGraphDataPos;

    virtual void draw(QCPPainter *painter) Q_DECL_OVERRIDE;
    virtual void drawGraphScatterPlot(QCPPainter *painter, QCPGraph *graph, const QPointF &pos);

    int pickClosest(double target, const QVector<double> &vector);
};

源文件


 QCPToolTip

QCPToolTip::QCPToolTip(QCustomPlot *parentPlot)
    : QCPAbstractItem(parentPlot),
      position(createPosition(QLatin1String("position"))),
      mPlotReplot(true),
      mTextAlignment(Qt::AlignLeft | Qt::AlignVCenter),
      mRadius(6, 6),
      mSizeMode(Qt::AbsoluteSize),
      mHighlightGraph(nullptr)
{
    position->setType(QCPItemPosition::ptAbsolute);
    setSelectable(false);
    setLayer("overlay");

    setBorderPen(Qt::NoPen);
    setBrush(QColor(87, 98, 93, 180));
    setTextColor(Qt::white);
    setOffset(20, 20);
    setPadding(QMargins(6, 6, 6, 6));
    connect(mParentPlot, SIGNAL(mouseMove(QMouseEvent *)), this, SLOT(handleTriggerEvent(QMouseEvent *)));
}
void QCPToolTip::setText(const QString &text)
{
    mText = text;
};
void QCPToolTip::setFont(const QFont &font)
{
    mFont = font;
};
void QCPToolTip::setTextColor(const QColor &color)
{
    mTextColor = color;
};
void QCPToolTip::setBorderPen(const QPen &pen)
{
    mBorderPen = pen;
};
void QCPToolTip::setBrush(const QBrush &brush)
{
    mBrush = brush;
};
void QCPToolTip::setRadius(double xRadius, double yRadius, Qt::SizeMode mode /*= Qt::AbsoluteSize*/)
{
    mRadius = QPointF(xRadius,yRadius);
    mSizeMode = mode;
};
void QCPToolTip::setOffset(double xOffset, double yOffset)
{
    mOffset = QPointF(xOffset,yOffset);
};
void QCPToolTip::setPadding(const QMargins &paddings)
{
    mPadding = paddings;
};

void QCPToolTip::handleTriggerEvent(QMouseEvent *event)
{
    updatePosition(event->pos(), true);   // true 表示需要单独刷新,将调用update函数
}

void QCPToolTip::update()
{
    mPlotReplot = false;    // 表明单独刷新
    layer()->replot();
    mPlotReplot = true;    // 单独刷新完毕
}

// 不需要鼠标点击测试,因为ToolTip是跟随鼠标的,鼠标点击不到
double QCPToolTip::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
{
    Q_UNUSED(pos)
    Q_UNUSED(onlySelectable)
    Q_UNUSED(details)
    return -1;
}
int QCPToolTip::pickClosest(double target, const QVector<double> &vector)
{
    if (vector.size() < 2)
        return 0;

    // 查找第一个大于或等于target的位置
    auto it = std::lower_bound(vector.constBegin(), vector.constEnd(), target);

    if (it == vector.constEnd()) return vector.size() - 1;
    else if (it == vector.constBegin()) return 0;
    else return target - *(it - 1) < *it - target ? (it - vector.constBegin() - 1): (it - vector.constBegin());
}

void QCPToolTip::updatePosition(const QPointF &newPos, bool replot)
{
    mHighlightGraph = nullptr;
    double tolerance = mParentPlot->selectionTolerance();

    for (int i = mParentPlot->graphCount() - 1; i >= 0; --i)
    {
        QCPGraph *graph = mParentPlot->graph(i);
        // graph不可见或者scatter style 为空的时候,不显示ToolTip
        if (!graph->realVisibility() || graph->scatterStyle().isNone())
            continue;

        double limitDistance = tolerance;   // limitDistance 用于选择的范围
        double penWidth = graph->pen().widthF();
        QCPScatterStyle scatterStyle = graph->scatterStyle();

        limitDistance = qMax(scatterStyle.size(), tolerance);
        penWidth = scatterStyle.isPenDefined() ? scatterStyle.pen().widthF() : penWidth;

        // details会返回最接近的一个数据点,selectTest是不精确的,所以后面还要判断
        QVariant details;
        double currentDistance = graph->selectTest(newPos, false, &details);

        QCPDataSelection selection = details.value<QCPDataSelection>();
        if (currentDistance >= 0 && currentDistance < limitDistance + penWidth && !selection.isEmpty())
        {
            // 取出当前key和value值,并且转换为像素位置
            double key = graph->dataMainKey(selection.dataRange().begin());
            double value = graph->dataMainValue(selection.dataRange().begin());
            QPointF pos = graph->coordsToPixels(key, value);

            QRectF rect(pos.x() - limitDistance * 0.5, pos.y() - limitDistance * 0.5, limitDistance, limitDistance);
            rect = rect.adjusted(-penWidth, -penWidth, penWidth, penWidth);

            // 判断鼠标位置是否在数据点上
            if (rect.contains(newPos))
            {
                /*
                 * 解开以下注释,可以使得我们的文字跟轴标签的文字是一样的(但跟轴标签实际的显示效果可能是不一样的,
                 * 这里要注意,例如对于科学计数法,轴可能会使用美化),同时要注意当轴标签不显示的时候tickVectorLabels
                 * 返回的是空的,所以我们要做一下判断
                */
                // 注意这里的方式是不精确的,适用于文字轴这种类型的
                /*int keyIndex = pickClosest(key, graph->keyAxis()->tickVector());
                setText(QString("%1:%2").arg(graph->keyAxis()->tickVectorLabels().at(keyIndex),
                                         QString::number(value)));*/

                setText(QString("%1:%2").arg(QString::number(key), QString::number(value)));
                mHighlightGraph = graph;
                mGraphDataPos = pos;

                mParentPlot->setCursor(Qt::PointingHandCursor);
                position->setPixelPosition(newPos);  // 更新位置
                setVisible(true);

                if (replot) {
                    update();
                }
                break;
            }
        }
    }

    if (!mHighlightGraph && visible())
    {
        mParentPlot->setCursor(Qt::ArrowCursor);
        setVisible(false);
        if (replot) {
            update();
        }
    }
}
void QCPToolTip::draw(QCPPainter *painter)
{
    if (mPlotReplot) {  // 当前是由QCustomPlot的replot函数刷新的,所以要更新位置
        updatePosition(position->pixelPosition(), false);  // false表明不刷新
        if (!visible()) {
            // 由于位置更新之后,ToolTip可能会隐藏掉了,所以此处直接返回
            return;
        }
    }

    drawGraphScatterPlot(painter, mHighlightGraph, mGraphDataPos);

    QPointF pos = position->pixelPosition() + mOffset;
    painter->translate(pos);  // 移动painter的绘制原点位置

    QFontMetrics fontMetrics(mFont);
    QRect textRect = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | mTextAlignment, mText);
    textRect.moveTopLeft(QPoint(mPadding.left(), mPadding.top()));

    QRect textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom());
    textBoxRect.moveTopLeft(QPoint());

    // 限制ToolTip不超过QCustomPlot的范围
    if (pos.x() + textBoxRect.width() >= mParentPlot->viewport().right())
        painter->translate(-mOffset.x() * 2 - textBoxRect.width(), 0);
    if (pos.y() + textBoxRect.height() * 2 >= mParentPlot->viewport().bottom())
        painter->translate(0, -mOffset.y() * 2 - textBoxRect.height());

    // 绘制背景和边框
    if ((mBrush != Qt::NoBrush && mBrush.color().alpha() != 0) ||
            (mBorderPen != Qt::NoPen && mBorderPen.color().alpha() != 0))
    {
        double clipPad = mBorderPen.widthF();
        QRect boundingRect = textBoxRect.adjusted(-clipPad, -clipPad, clipPad, clipPad);

        painter->setPen(mBorderPen);
        painter->setBrush(mBrush);
        painter->drawRoundedRect(boundingRect, mRadius.x(), mRadius.y(), mSizeMode);
    }

    // 绘制文字
    painter->setFont(mFont);
    painter->setPen(mTextColor);
    painter->setBrush(Qt::NoBrush);
    painter->drawText(textRect, Qt::TextDontClip | mTextAlignment, mText);
}

void QCPToolTip::drawGraphScatterPlot(QCPPainter *painter, QCPGraph *graph, const QPointF &pos)
{
    if (!graph) return;

    QCPScatterStyle style = graph->scatterStyle();
    if (style.isNone()) return;

    if (graph->selectionDecorator())  // 如果有select decorator,则使用修饰器的风格
        style = graph->selectionDecorator()->getFinalScatterStyle(style);

    style.applyTo(painter, graph->pen());
    style.setSize(style.size() * 1.2); // 放大一点
    style.drawShape(painter, pos);
}

使用方法

if(m_tip == nullptr){
    m_tip = new QCPToolTip(m_plotLine);//m_plotLine is QCustomPlot
}

原文地址

https://blog.csdn.net/qq10097355/article/details/105048146

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/365078.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

操作系统--Linux内核进程间的通信方式

每个进程的用户地址空间都是独立的&#xff0c;一般而言是不能互相访问的&#xff0c;但内核空间是每个进程都共享的&#xff0c;所以进程之间要通信必须通过内核。 一、管道 匿名管道&#xff1a;它没有名字标识&#xff0c;匿名管道是特殊文件只存在于内存&#xff0c;没有存…

二、Gradle 与 Idea 整合

这里写自定义目录标题 1、Groovy简介2、Groovy 安装3、创建 Groovy 项目4、Groovy 基本语法5、在 idea 中创建普通 java 工程 1、Groovy简介 详细了解请参考&#xff1a;http://www.groovy-lang.org/documentation.html 2、Groovy 安装 下载后解压到本地 验证&#xff1a; …

springboot完成一个线上图片存放地址+实现前后端上传图片+回显

1.路径 注意路径 2.代码&#xff1a;&#xff08;那个imagePath没什么用&#xff0c;懒的删了&#xff09;&#xff0c;注意你的本地文件夹要有图片&#xff0c;才可以在线上地址中打开查看 package com.xxx.common.config;import org.springframework.beans.factory.annotat…

HTML+CSS+JS的3D进度条

<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>HTMLCSSJS的3D进度条</title><style>…

高效的跳表

高效的跳表 一、 概念二、 实现原理三、存在的问题四、解决方法五、如何保证效率六、代码实现七、总结对比平衡搜索树对比哈希表 一、 概念 跳表&#xff0c;是一种用来查询数据的数据结构&#xff0c;它是由William Pugh发明的&#xff0c;借助有序链表&#xff0c;来实现高效…

git 小乌龟解决冲突问题

git 解决冲突 下边命令我指的是小乌龟的命令&#xff0c;不是指的git的命令行语句 git commit 提交代码到本地仓库 git pull 拉代码 git push 推代码的时候出现代码冲突问题 自动合并失败了&#xff0c;有冲突的文件&#xff0c;需要先解决冲突。修改标记为已解决&#xff0…

轻松玩转书生·浦语大模型趣味Demo(二)

大模型及 InternLM 模型介绍 什么是大模型 人工智能领域中参数数量巨大&#xff0c;拥有庞大计算能力和参数规模的模型 特点及应用 利用大量数据进行训练拥有数十亿甚至数千亿个参数模型在各种任务中展现出惊人的性能 InternLM-Chat-7B 智能对话 Demo Legant 介绍 lagnet …

Flutter 应用服务:主题、暗黑、国际化、本地化 - app_service库

Flutter应用服务 主题、暗黑、国际化、本地化 app_service库 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/det…

Debezium发布历史103

原文地址&#xff1a; https://debezium.io/blog/2021/03/18/understanding-non-key-joins-with-quarkus-extension-for-kafka-streams/ 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. 了解 Kafka Streams 的 Q…

2024美赛数学建模A题思路分析 - 资源可用性和性别比例

1 赛题 问题A&#xff1a;资源可用性和性别比例 虽然一些动物物种存在于通常的雄性或雌性性别之外&#xff0c;但大多数物种实质上是雄性或雌性。虽然许多物种在出生时的性别比例为1&#xff1a;1&#xff0c;但其他物种的性别比例并不均匀。这被称为适应性性别比例的变化。例…

书客、米家、柏曼大路灯哪款好?多维度实测对比推荐!

每到寒暑假&#xff0c;各个论坛上出现“大路灯怎么选”的类似话题非常频繁&#xff0c;因为现在的孩子出来上学期间需要读写之外&#xff0c;在寒暑假时也在不断的学习&#xff0c;许多家长关注到孩子学习时的光线问题&#xff0c;担心影响到孩子的视力状况&#xff0c;都纷纷…

pdf怎么标注?这3个标注方法亲测好用

pdf怎么标注&#xff1f;在日常办公中&#xff0c;PDF标注软件发挥着重要作用。当我们收到一份PDF文档&#xff0c;如合同、报告或电子书&#xff0c;需要对其进行批注、修改或解释时&#xff0c;一款好用的PDF标注软件就显得尤为重要。通过这些软件&#xff0c;我们可以轻松地…

【机器学习】贝叶斯垃圾邮件识别

实验三&#xff1a;贝叶斯垃圾邮件识别 本次作业以垃圾邮件分类任务为基础&#xff0c;要求提取文本特征并使用朴素贝叶斯算法进行垃圾邮件识别&#xff08;调用已有工具包或自行实现&#xff09;。 1 任务介绍 ​ 电子邮件是互联网的一项重要服务&#xff0c;在大家的学习、…

操作系统-02-Ubuntu 常用命令等汇总

Ubuntu Ubuntu是一个开源软件平台&#xff0c;可以在从智能手机、平板电脑和个人电脑到服务器和云端等各种设备上运行。 ubuntu Title: Desktop Ubuntu desktop->command: CtrlAltF2~F6 command->desktop: CtrlAltF7 Login Change to root user $ sudu suLogin to al…

springboot 集成 nacos (demo 版)

环境要求&#xff1a;本地安装 nacos&#xff08;windows版本&#xff09;&#xff0c;jdk 是 8&#xff0c;安装完还得在 nacos 上面配置一个 yaml 类型的配置文件&#xff0c;方便项目演示读取。 提示&#xff1a;如标题所示&#xff0c;本文只介绍如何从零到一的去集成 naco…

【C/Python】GtkApplicationWindow

一、C语言 GtkApplicationWindow 是 GTK 库中用于创建应用程序主窗口的一个控件。 首先&#xff0c;需要确保环境安装了GTK开发库。然后&#xff0c;以下是一个简单的使用 GtkApplicationWindow 创建一个 GTK 应用程序的示例&#xff1a; #include <gtk/gtk.h>static …

nvm - nodejs版本管理工具

我们可能同时在进行2个或者多个不同的项目开发&#xff0c;每个项目的需求不同&#xff0c;进而不同项目必须依赖不同版本的NodeJS运行环境&#xff0c;这种情况下&#xff0c;对于维护多个版本的node将会是一件非常麻烦的事情&#xff0c;nvm就是为解决这个问题而产生的&#…

9.2爬楼梯(LC70-E)

算法&#xff1a; 多举几个例子&#xff0c;找规律&#xff1a; 爬到第一层楼梯有一种方法&#xff0c;爬到二层楼梯有两种方法。 那么第一层楼梯再跨两步就到第三层 &#xff0c;第二层楼梯再跨一步就到第三层&#xff08;时序&#xff09;。 所以到第三层楼梯的状态可以由…

k8s存储之PV、PVC

在k8s集群中&#xff0c;资源存储会散落到各个工作节点上&#xff0c;这样对用资源调用很不方便&#xff0c;那么k8s是如何实现存储资源共享的呢&#xff0c;本文浅尝辄止的探讨一下&#xff0c;k8s是通过pv、pvc实现的。 一、PV、PVC的概念 1、持久卷(PV&#xff09; pv是Pe…

闲人闲谈PS之五十三——离散制造中的魔鬼--物料套裁

惯例闲话&#xff1a;最近和老婆大人商议买车事宜&#xff0c;闲人以为会陷入买油车还是电车的纠结&#xff0c;没想到老婆大人无比坚定&#xff0c;买电车。在买车这方面&#xff0c;老婆的想法居然比闲人超前。闲人对车定位在代步工具&#xff0c;2年前&#xff0c;对车还是印…