使用QCustomPlot在数据曲线上设置可随鼠标移动的游标

QCustomPlot(以下简称QCP)绝对是使用Qt开发时绘制数据曲线的福利工具,简单的几行代码就能很好地绘制2d数据曲线。
但有时有这样的应用需求:曲线绘制好了,用户需要利用鼠标在图上移动来拾取曲线上的数据。这个需求可以通过在数据曲线上设置游标,使游标响应鼠标移动来解决。
怎么做到呢?csdn上有部分文章,我觉得有的讲得复杂,有的也没讲清楚。
下面讲一下我的实践情况,希望对大家有帮助。

一、绘图,并添加游标(tracer)和游标数据说明(tracerLabel)

以使用QWidget为例。继承一个QWidget,加入QCP的指针作为成员(cmPlot),它就是负责绘制曲线的对象,可以称之为绘制器。为了实现随鼠标移动的游标功能,还要加入QCPItemTracer的指针(tracer)和QCPItemText的指针(tracerLabel)作为成员,前者就是游标,后者用于显示游标在曲线上的数据。代码如下:

widget.h

#include <QWidget>
#include <QMouseEvent>

#include "../QCustomPlot.h"

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    ~Widget();

public slots:
    void mouseMove1(QMouseEvent *e);
    void mouseMove2(QMouseEvent *e);

private:
    QCustomPlot *cmPlot;
    QCPItemTracer *tracer;
    QCPItemText *tracerLabel;
};

#endif // WIDGET_H

说明一下,widget.h中还申明了两个槽函数
mouseMove1() 和 mouseMove2()
这是两个实现游标的方法,这两个方法不兼容,使用其中一个,就不要用另外一个,使用时根据情况选择。具体情况后续会介绍。
一个简单的绘制数据曲线的方法就是实现widget的构造函数,如下:

Widget::Widget(QWidget *parent)
    : QWidget(parent),
      cmPlot(new QCustomPlot)
{
    QVBoxLayout *vbox = new QVBoxLayout(this); //生成一个布局
    vbox->addWidget(cmPlot); //把绘制器加入布局

    QPushButton *btn = new QPushButton(QString("This is a push button"), this); //生成一个按钮,没有什么用,配相而已
    vbox->addWidget(btn); //把按钮加入布局

    setGeometry(100, 100, 800, 500);

    QVector<double> xs, ys; //要绘制的数据
    for(double x=0.0; x<2.0*M_PI; x+=M_PI/200)
    {
        //看得出来,数据就是典型正弦曲线上的数据
        xs.append(x);
        ys.append(sin(x));
    }

    cmPlot->xAxis->setRange(-0.2, 2.0*M_PI+0.2);
    cmPlot->yAxis->setRange(-1.2, 1.2);
    cmPlot->addGraph();
    cmPlot->graph(0)->setData(xs, ys); //把数据加入到绘制器cmPlot,绘制器会自动绘制曲线

    tracer = new QCPItemTracer(cmPlot); //生成游标
    //下面的代码就是设置游标的外观
    tracer->setPen(QPen(Qt::red));
    tracer->setBrush(QBrush(Qt::red));
    tracer->setStyle(QCPItemTracer::tsCircle);
    tracer->setSize(20.0);

    tracerLabel = new QCPItemText(cmPlot); //生成游标说明
    //下面的代码就是设置游标说明的外观和对齐方式等状态
    tracerLabel->setLayer("overlay");
    tracerLabel->setPen(QPen(Qt::red));
    tracerLabel->setPositionAlignment(Qt::AlignLeft | Qt::AlignTop);
    //下面这个语句很重要,它将游标说明锚固在tracer位置处,实现自动跟随
    tracerLabel->position->setParentAnchor(tracer->position);

    //这里的信号-槽连接语句很重要
    connect(cmPlot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMove1(QMouseEvent*)));
    //connect(cmPlot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMove2(QMouseEvent*)));
}

上述代码大部分都好懂。重点就是信号-槽的连接部分,下面说一下。

二、通过信号-槽连接来处理鼠标事件(获得鼠标位置)

一般来说,要获得鼠标移动时的实时位置,一般可以通过改写widget的事件函数 mouseMoveEvent() 来实现。但是如果直接这样做的话,实际上不可行,因为鼠标是在绘制器(cmPlot)上移动时,该事件已经由绘制器处理了,这造成widget的鼠标移动事件函数得不到执行。

要处理这个情况也很简单。当绘制器处理鼠标移动时,它会发送 mouseMove(QMouseEvent*) 信号,只要为widget设计槽函数来接受这个信号,widget 就能够处理鼠标事件了。

所以widget.h中申明了两个槽函数
mouseMove1() 和 mouseMove2()
并用connect语句将cmPlot发出的信号mouseMove(QMouseEvent*)连接到上述两个槽函数上。
如前所述,这两个槽函数代表两种实现方法,而且不兼容,所以实现其中一个,就要将另外一个注释掉,切记。

三、方法1:手动设置tracer的位置
在槽函数mouseMove1()中实现方法1。

void Widget::mouseMove1(QMouseEvent *e)
{
    //获得鼠标位置处对应的横坐标数据x
    double x = cmPlot->xAxis->pixelToCoord(e->pos().x());
    double xValue, yValue;
    //xValue就是游标的横坐标
    xValue = x;
    //yValue就是游标的纵坐标,这里直接根据产生数据的函数(sin)获得
    yValue = sin(xValue);
    //下面设置游标(tracer)的位置
    tracer->position->setCoords(xValue, yValue);
    //设置游标说明(tracerLabel)的内容
    tracerLabel->setText(QString("x = %1, y = %2").arg(xValue).arg(yValue));
    cmPlot->replot();//绘制器一定要重绘,否则看不到游标位置更新情况
}

上面代码也比较好懂。说明一下,这种方法是手动自由设置游标的位置,这个位置可以跟曲线数据没有什么关系。之所以我们可以看到游标粘附在曲线上,是因为我们获得了xValue,并利用xValue和曲线函数计算出了yValue,再利用xVaule和yValue设置了游标的位置,这样游标就粘附在曲线上了,并跟随鼠标移动。如下:
在这里插入图片描述
这种方法适合于函数比较简单的情况(比如本例的sin),计算yValue时计算开销不大,鼠标移动时响应比较迅速。如果函数形式比较复杂,计算量大,那么应该采用以下的方法2。

四、方法2:将tracer自动粘附到曲线
在槽函数 mouseMove2() 中实现方法2。代码如下:

void Widget::mouseMove2(QMouseEvent *e)
{
    //获得鼠标位置处对应的横坐标数据x
    double x = cmPlot->xAxis->pixelToCoord(e->pos().x());
    //下面的代码很关键
    tracer->setGraph(cmPlot->graph(0)); //将游标和该曲线图层想连接
    tracer->setGraphKey(x); //将游标横坐标(key)设置成刚获得的横坐标数据x
    tracer->setInterpolating(true); //游标的纵坐标可以通过曲线数据线性插值自动获得(这就不用手动去计算了)
    tracer->updatePosition(); //使得刚设置游标的横纵坐标位置生效
    //以下代码用于更新游标说明的内容
    double xValue = tracer->position->key();
    double yValue = tracer->position->value();
    tracerLabel->setText(QString("x = %1, y = %2").arg(xValue).arg(yValue));
    cmPlot->replot(); //不要忘了重绘
}

这个方法也比较简单,可以自动根据曲线数据给出游标位置,就不用再算一次了,节省了计算开销。

全文完,上述经验希望对大家有用。

QCustomPlot 是一个强大的 Qt 库,专为创建高质量的绘图和数据分析应用设计。它提供了一个灵活且功能丰富的 API,支持绘制多条曲线、添加交互式元素如游标和标注,以及自定义图表外观。 在 QCustomPlot 中,要添加多条曲线游标,你可以使用 `QCPPlot` 类,并结合 `QCPItemTracer` 和 `QCPItemScatterer` 来实现。以下是基本步骤: 1. **创建 QCPPlot**:首先,在你的窗口中添加一个 `QCustomPlot` 实例,并设置其属性,比如坐标轴范围和标题。 2. **添加曲线**:使用 `QCPGraph` 添加多条数据系列,每条曲线代表一种数据类型。例如: ```cpp QCPGraph *graph = new QCPGraph(); graph->setPen(QPen(Qt::blue, 1.0f)); ``` 3. **添加数据**:将你的数据点添加到相应的 `QCPGraph` 上。数据可以是数组或列表形式。 4. **创建游标**:为了添加游标,你可以使用 `QCPItemTracer` 或 `QCPItemScatterer`。`QCPItemTracer`适用于跟随单个数据点,而 `QCPItemScatterer`则可以同时跟踪多个点: ```cpp QCPItemTracer *tracer = new QCPItemTracer(graph); tracer->setPen(QPen(Qt::red, 1.0f)); plot->addItem(tracer); ``` 5. **配置游标行为**:设置游标的样式(线型、颜色等),以及当游标移动时触发的事件。 6. **显示图表**:调用 `plot->replot()` 更新图表内容。 相关问题: 1. QCustomPlot 的图形元素有哪些? 2. 如何设置 QCPItemTracer移动事件处理? 3. 如何通过代码动态改变游标的跟踪点?
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值