Qt-QCustomplot图像设计功能-游标

文章介绍了如何在QCustomPlot中使用QCPItemStraightLine类创建游标,以及如何通过鼠标事件实现游标的交互功能,包括鼠标点击、移动和释放事件。游标的使用场景包括计算曲线图上数据的平均值和面积,提供了增强的数据分析和可视化交互性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

系列文章目录



前言

曲线图数据处理离不开游标的使用,利用游标可以进行区间选择,对该区间的数据进行随意处理,例如计算该区间的平均值、面积、方差、拖动、移动等操作。在 Qt 中,曲线图是一种常见的数据可视化方式,它可以用来展示随时间或其他变量而变化的数据。为了提供更多交互性和准确性,Qt 的曲线图通常支持游标功能。


一、什么是游标?

游标是一个可移动的垂直线或标记,用于在曲线图上指示特定数据点或位置。用户可以通过拖动游标,精确查看曲线图上不同数据点的数值。游标可以帮助用户分析曲线图中的数据,并提供更精确的读取和放大功能。

游标的功能:

  1. 数据展示:游标可以在曲线图上沿着 X 轴(时间轴)或 Y 轴(数值轴)移动,以显示所选数据点的具体数值。
  2. 交互操作:用户可以通过鼠标拖动游标来选择感兴趣的数据点并获取详细信息。
  3. 数据对比:通过添加多个游标,用户可以同时比较不同数据点之间的数值差异,从而更好地理解曲线图中的趋势和关系。

二、QCustomPlot中游标的实现。

游标截图

1.QCPItemStraightLine

QCPItemStraightLine 是 Qt 的 QCustomPlot 库中用于在曲线图中添加直线标记的类。它通过设置起点和终点坐标来确定直线的位置和长度,并支持自定义样式和交互行为。通过使用 QCPItemStraightLine,您可以在曲线图中增加参考线或标记特定位置,以提高数据可视化和用户交互性。

代码如下:

	//定义全局变量
    QCPItemStraightLine *m_lineLV; //左游标
    QCPItemStraightLine *m_lineRV; //右游标
    QCPItemStraightLine *m_lineH; //水平游标
/*******************
 * @author xxx
 * @method
 * @param
 * 初始化游标
*******************/
void Integrate::PlotWaterfall()
{
    try {
        //默认曲线选择
        graph = customPlot->graph(0);

        //设置游标初始位置(可以不设置)
        QCPRange keyRange = customPlot->axisRect()->axis(QCPAxis::atBottom)->range();
        xValue1 = keyRange.upper * 0.2;
        xValue2 = keyRange.upper * 0.7;
        QCPRange keyRange2 = customPlot->axisRect()->axis(QCPAxis::atLeft)->range();
        yValue = keyRange2.lower;
        auto iter1 = graph->data()->findBegin(xValue1);
        xValue1 = iter1->mainKey();
        auto iter2 = graph->data()->findBegin(xValue2);
        xValue2 = iter2->mainKey();

        //左游标
        QPen pen(graph->pen().color(),1.5,Qt::SolidLine); //颜色 、宽度、样式(直线)
        m_lineLV = new QCPItemStraightLine(customPlot);
        m_lineLV->setLayer("overlay");
        m_lineLV->setPen(pen);//设置游标线的样式
        m_lineLV->setClipToAxisRect(true);//自适应范围
        m_lineLV->point1->setCoords(xValue1, 1);//起点坐标
        m_lineLV->point2->setCoords(xValue1, 1);//终点坐标
        m_lineLV->setVisible(true);

        //右游标
        m_lineRV = new QCPItemStraightLine(customPlot);
        m_lineRV->setLayer("overlay");
        m_lineRV->setPen(pen);
        m_lineRV->setClipToAxisRect(true);
        m_lineRV->point1->setCoords(xValue2, 1);
        m_lineRV->point2->setCoords(xValue2, 1);
        m_lineRV->setVisible(true);

        //水平游标
        m_lineH = new QCPItemStraightLine(customPlot);
        m_lineH->setLayer("overlay");
        m_lineH->setPen(pen);
        m_lineH->setClipToAxisRect(true);
        m_lineH->point1->setCoords(0, yValue);
        m_lineH->point2->setCoords(keyRange.upper, yValue);
        m_lineH->setVisible(true);

		//编辑框显示初始位置
        ui->lineEdit_2->setText(QString::number(m_lineLV->point1->key()));
        ui->lineEdit_3->setText(QString::number(m_lineRV->point1->key()));
        ui->lineEdit_4->setText(QString::number(m_lineH->point1->value()));

        customPlot->replot();
    } catch (...) {
        FERROR("游标初始化异常!");
    }

}

至此在图像上就可以生成看到三条游标了,但是此时的游标并不能进行鼠标操作,貌似没有封装好的方法提供,需要手写。

三、游标的鼠标事件。

游标事件

代码如下(示例):

		//建立槽函数
        connect(customPlot, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress(QMouseEvent*)));
        connect(customPlot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseMove(QMouseEvent*)));
        connect(customPlot, SIGNAL(mouseRelease(QMouseEvent*)), this, SLOT(mouseRelease(QMouseEvent*)));

1.鼠标点击事件

/*******************
 * @author xxx
 * @method
 * @param
 * 鼠标抓取事件 点击曲线游标显示
*******************/
void Integrate::mousePress(QMouseEvent *event)
{
    try {
    	//定义鼠标距离游标的像素距离
        double distanceLV = m_lineLV->selectTest(event->pos(), false);
        double distanceRV = m_lineRV->selectTest(event->pos(), false);
        double distanceH  = m_lineH->selectTest(event->pos(), false);
		
		//定义游标标识,由上面的距离来决定移动那根游标
        if (event->button() == Qt::LeftButton && distanceLV <= 5 && customPlot->axisRect()->rect().contains(event->pos())){
            lLineV = true;
        }
        if (event->button() == Qt::LeftButton && distanceRV <= 5 && customPlot->axisRect()->rect().contains(event->pos())){
            rLineV = true;
        }
        if (event->button() == Qt::LeftButton && distanceH <= 5 && customPlot->axisRect()->rect().contains(event->pos())){
            bLineH = true;
        }
        //左右游标重合时,先移动右游标(非必须)
        if(event->button() == Qt::LeftButton){//鼠标左键判断
            if(customPlot->axisRect()->rect().contains(event->pos())){//鼠标点在图像内
                if(distanceLV <= 5 && distanceRV <= 5){//重合时
                	//这里获取曲线的最后一个值,以判断重合位置是否在末尾,如果是则先移动左游标,否则都先移动右游标
                    double end = tempXpoinLists[subscript].last();
                    double now = m_lineLV->point1->key();
                    if(now == end){
                        lLineV = true;
                        rLineV = false;
                    }else{
                        lLineV = false;
                        rLineV = true;
                    }
                }
                //三线重合时,先移动水平游标(非必须)
                if(distanceLV <= 5 && distanceRV <= 5 && distanceH <= 5){
                    lLineV = false;
                    rLineV = false;
                    bLineH = true;
                }
            }
        }
        customPlot->replot();
    } catch (...) {
        FERROR("系统发异常!");
    }
}

2.鼠标移动事件

/*******************
 * @author xxx
 * @method
 * @param
 * 游标移动
*******************/
void Integrate::mouseMove(QMouseEvent *event)
{
    try {
        //当前鼠标位置(像素坐标)
        double x_pos = event->pos().x();
        double y_pos = event->pos().y();

        //像素坐标转成实际的x,y轴的坐标
        double x_val = customPlot->xAxis->pixelToCoord(x_pos);
        double y_val = customPlot->yAxis->pixelToCoord(y_pos);
		
		//获取当前曲线上最近的X轴点坐标
        auto iter = graph->data()->findBegin(x_val);
        x_val = iter->mainKey();

		//获取当前游标X轴的坐标
        double r_point = m_lineRV->point1->key();
        double l_point = m_lineLV->point1->key();
 		//获取距离
        double distanceLV = m_lineLV->selectTest(event->pos(), false);
        double distanceRV = m_lineRV->selectTest(event->pos(), false);
        double distanceH  = m_lineH->selectTest(event->pos(), false);
		//当鼠标在游标上时改变鼠标形状
        if (distanceLV <= 5 || distanceRV <= 5 || distanceH <= 5 && customPlot->axisRect()->rect().contains(event->pos())){
            customPlot->setCursor(Qt::ClosedHandCursor);
        }else{
            customPlot->setCursor(Qt::ArrowCursor);
        }
		
		//游标移动判断
        if(lLineV)
        {//移动左游标
            if(x_val > r_point){ //跟右游标左判断,限制边界
                m_lineLV->point1->setCoords(r_point, customPlot->yAxis->range().lower);
                m_lineLV->point2->setCoords(r_point, customPlot->yAxis->range().upper);
            }else {
                m_lineLV->point1->setCoords(x_val, customPlot->yAxis->range().lower);
                m_lineLV->point2->setCoords(x_val, customPlot->yAxis->range().upper);
            }
            ui->lineEdit_2->setText(QString::number(m_lineLV->point1->key()));
            customPlot->replot();
        }
        if(rLineV)
        {
            if (x_val < l_point) {
                m_lineRV->point1->setCoords(l_point, customPlot->yAxis->range().lower);
                m_lineRV->point2->setCoords(l_point, customPlot->yAxis->range().upper);
            }
            else {
                m_lineRV->point1->setCoords(x_val, customPlot->yAxis->range().lower);
                m_lineRV->point2->setCoords(x_val, customPlot->yAxis->range().upper);
            }
            ui->lineEdit_3->setText(QString::number(m_lineRV->point1->key()));
            customPlot->replot();
        }
        if(bLineH)
        {
            m_lineH->point1->setCoords(customPlot->xAxis->range().lower,y_val);
            m_lineH->point2->setCoords(customPlot->xAxis->range().upper,y_val);
            ui->lineEdit_4->setText(QString::number(m_lineH->point1->value()));
            customPlot->replot();
        }
		//进行相应的计算
        if(lLineV || rLineV || bLineH){
            calculate_integral();
            calculate_average();
        }
    } catch (...) {
        FERROR("系统发异常!");
    }

}

3.鼠标释放事件

/**
 * @brief Average::mouseRelease
 * @param e
 * 鼠标释放事件 关闭游标移动
 */
void Integrate::mouseRelease(QMouseEvent *evet)
{
    lLineV = false;
    rLineV = false;
    bLineH = false;

}

四、 游标的应用场景

1. 曲线平均值计算

/*******************
 * @author xxx
 * @method
 * @param
 * 平均值计算
*******************/
void Integrate::calculate_average()
{
    try {
    	//1、获取左右游标位置坐标
        double r_point = m_lineRV->point1->key();
        double l_point = m_lineLV->point1->key();

        //曲线点
        int begin = 0, end = 0;
        double sum = 0;
        //2、通过两个坐标点获取分别获取在曲线数据点中的下标位置
        for (int i = 0; i < tempXPoinLists[subscript].length(); ++i) {
            if(tempXPoinLists[subscript][i] == l_point){
                begin = i;
            }
            if(tempXPoinLists[subscript][i] == r_point){
                end = i;
                break;
            }
        }
        //3、根据两下标位置获取其之间的所有值 再计算平均值
        int n = end - begin + 1;
        for (int i = begin; i <= end; ++i) {
            sum += tempYPointLists[subscript][i];//曲线中的数据总和
        }
        averge = sum / n;//计算出平均值
        
        ui->avergrValue->setText(QString::number(averge,'E',2));
        ui->label_9->setText(QString::number(n));
        int range = tempXPoinLists[subscript][end] - tempXPoinLists[subscript][begin];
        ui->label_11->setText(QString::number(range));
    } catch (...) {
        FERROR("系统发异常!");
    }
}

2.计算面积

/*******************
 * @author xxx
 * @method
 * @param
 * 面积值计算
*******************/
void Integrate::calculate_integral()
{
    try {
        //1、获取两游标当前的坐标点
        int begin = 0, end = 0;
        double r_point = m_lineRV->point1->key();
        double l_point = m_lineLV->point1->key();
        //2、根据坐标点找到曲线种对应的数据点位置  begin是曲线种第一个点位置  end是最后一个点位置
        for (int i = 0; i < tempXPoinLists[subscript].length(); ++i) {
            if(tempXPoinLists[subscript][i] == l_point){
                begin = i;
            }
            if(tempXPoinLists[subscript][i] == r_point){
                end = i;
                break;
            }
        }
        int n = end - begin +1;
        //我这里采用的是梯形计算法,也可以采用积分等面积算法进行计算,根据自己的需求选择
        //3、根据这两个点位置区间计算所有的形成梯形面积  面积公式:(上底 + 下底)* 高 / 2;
        double upper; //上底
        double lower; //下底
        double high;  //高
        double area = 0;  //面积
        for (int i = begin; i < end; ++i) {
            upper = tempYPoinLists[subscript][i];
            lower = tempYPoinLists[subscript][i+1];
            high = tempXPoinLists[subscript][i+1] -  tempXPoinLists[subscript][i];
            area += (upper+ lower)*high / 2;
        }
        ui->integralValue->setText(QString::number(area,'E',2));
    } catch (...) {
        FERROR("系统发异常!");
    }
}

总结

本文讲述了在QCustomPlot中建立游标和游标的鼠标事件以及游标使用的一些简单场景,游标的框架使用的是QCustomPlot中的QCPItemStraightLine库,该库的本意是创建一个直线项目创建的项目将自动注册到父绘图中。除此之外,还有其他绘制游标的类,大家可多多尝试。

基础概念-Qt QCustomPlot 介绍
Qt-QCustomplot图像和QChart的区别

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值