QT系列之曲线图绘制(推荐QCustomPlot)
阅读原文时间:2021年04月18日阅读:1

Qt做曲线图绘制有很多类,如QChart,QCustomPlot和QWT。我之前一直用QChart,但是感觉它在数据量大的时候会有点卡顿,甚至有时候会直接让程序崩溃。所以推荐还是一步到位,直接用其他两种吧!

这篇博客的示例代码见咸鱼:

1.一个好的曲线图应该具有的功能

1.美观——背景,线条颜色,线条宽度,字体,边框间距,坐标轴间隔;

2.以鼠标为中心进行缩放,以及重置曲线图;

3.显示鼠标点所在坐标值;

4.图中曲线的名称图例;

5.支持拖拽;

6.支持添加预警线;

7.支持多通道数据显示;

8.支持数据统计功能;——最大值,最小值,平均值,方差。

9.支持自定义曲线的显示;——图例及图例框位置、效果、字体、颜色、X轴标签文字、背景颜色、背景标签线大小

10.支持框选折线图,筛选表中数据的功能;

2.QChart的边界定义

2.1 效果对比

下图左是不加如下的代码,下图右是加了如下的代码效果。

    chart->setContentsMargins(0, 0, 0, 0);  //设置外边界全部为0
    chart->setMargins(QMargins(0, 0, 0, 0));//设置内边界全部为0

      

下图左是不加如下的代码,下图右是加了如下的代码效果。

chart->layout()->setContentsMargins(0, 0, 0, 0);//设置边界为0

             

3.QCustomPlot的使用

使用QCustomPlot需要先下载这个类,然后将.h和.cpp文件添加,最后将widget提升为QCustomPlot类。

3.1 QCustomPlot的简单使用

如下的代码包括:添加一个图例,添加图例的数据,设置坐标轴的显示范围。

//定义两个可变数组存放绘图的坐标数据
    QVector<double> x(101),y(101);//分别存放x和y坐标的数据,101为数据长度
    //添加数据,我们这里演示y=x^3,为了正负对称,我们x从-50到+50
    for(int i=0;i<101;i++)
    {
        x[i] = i-50;
        y[i] = x[i] * x[i] * x[i];
    }

    ui->widget->addGraph();//添加一条数据
    ui->widget->graph(0)->setData(x,y);//注意这里必须为QVector且是double类型的变量

    //设置坐标轴显示范围,否则我们只能看到默认的范围
    ui->widget->xAxis->setRange(-60,60);
    ui->widget->yAxis->setRange(-60*60*60-100,60*60*60+100);

运行效果图如下:

3.2 设置图例名称

下面代码的0代表第一个图例,如果有多个图例可以是1,2,3等等。新建图例是上面的addgraph()函数。

    //设置图例名称
    ui->widget->graph(0)->setName("line");
    ui->widget->legend->setVisible(true);

效果图如下:

3.3 设置背景,线条颜色,线条宽度,字体,坐标轴间隔;

各类图类的总结链接:https://blog.csdn.net/u014252478/article/details/80430000

详细的图层相关知识及背景设置见链接:https://blog.csdn.net/u014252478/article/details/79928433

图的外观设置见链接:https://segmentfault.com/a/1190000021931918

大佬的学习博客见链接:https://www.cnblogs.com/swarmbees/p/6060473.html

线条样式的设置见链接:https://blog.csdn.net/yxy244/article/details/100033549

坐标轴设置的详细博客见链接:https://blog.csdn.net/yxy244/article/details/100311112

第一,背景的设置

由于图层有多层,每层都有背景,下面的代码是设置我们通常理解的坐标轴背景颜色。

    //设置背景颜色
    QLinearGradient plotGradient;
    plotGradient.setStart(0, 0);
    plotGradient.setFinalStop(0, 350);
    plotGradient.setColorAt(0, QColor(80, 80, 80));
    plotGradient.setColorAt(1, QColor(50, 50, 50));
    //ui->widget->setBackground(plotGradient);
    ui->widget->axisRect()->setBackground(plotGradient);   // 设置QCPAxisRect背景颜色

效果图如下:

第二,设置线条颜色,宽度以及图刷

    //设置线条颜色
    QPen pen;
    pen.setWidth(3);//线宽
    pen.setColor(Qt::red);//颜色
    ui->widget->graph(0)->setPen(pen);
    //设置画刷:与0刻度线围成区域
    ui->widget->graph(0)->setBrush(QBrush(QColor(0, 0, 255, 100)));//后面的100是不透明度

效果图如下:

第三,设置坐标轴刻度线的风格

 //设置坐标轴刻度线
    ui->widget->xAxis->setBasePen(QPen(Qt::white, 1));  // 轴线的画笔
    ui->widget->xAxis->setTickPen(QPen(Qt::white, 1));  // 轴刻度线的画笔
    ui->widget->xAxis->setSubTickPen(QPen(Qt::white, 1)); // 轴子刻度线的画笔
    ui->widget->xAxis->setTickLabelColor(Qt::white);  // 轴刻度文字颜色
    ui->widget->xAxis->setLabel("x轴");  // 只有设置了标签,轴标签的颜色才会显示
    ui->widget->xAxis->setLabelColor(Qt::white);   // 轴标签颜色
    ui->widget->xAxis->setTickLengthIn(3);       // 轴线内刻度的长度
    ui->widget->xAxis->setTickLengthOut(5);      // 轴线外刻度的长度
    ui->widget->xAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);  // 设置轴线结束时的风格为 实角三角形但内部有凹陷的形状, setLowerEnding设置轴线开始时的风格

    ui->widget->yAxis->setBasePen(QPen(Qt::white, 1));  // 轴线的画笔
    ui->widget->yAxis->setTickPen(QPen(Qt::white, 1));  // 轴刻度线的画笔
    ui->widget->yAxis->setSubTickPen(QPen(Qt::white, 1)); // 轴子刻度线的画笔
    ui->widget->yAxis->setTickLabelColor(Qt::white);  // 轴刻度文字颜色
    ui->widget->yAxis->setLabel("y轴");  // 只有设置了标签,轴标签的颜色才会显示
    ui->widget->yAxis->setLabelColor(Qt::white);   // 轴标签颜色
    ui->widget->yAxis->setTickLengthIn(3);       // 轴线内刻度的长度
    ui->widget->yAxis->setTickLengthOut(5);      // 轴线外刻度的长度
    ui->widget->yAxis->setUpperEnding(QCPLineEnding::esSpikeArrow);  // 设置轴线结束时的风格为 实角三角形但内部有凹陷的形状, setLowerEnding设置轴线开始时的风格

效果图如下:

第四,字体设置

看不出变化,这个也不太重要吧。

//设置字体
    ui->widget->setFont(QFont(font().family(), 9));//设置文本的字体
    //setLabelFont:  设置label字体
    //setTickLabelFont:设置刻度label字体

第五,坐标轴间隔

//坐标轴间隔
    ui->widget->xAxis->setAutoTickStep(false); //先关闭自动间隔
    ui->widget->yAxis->setAutoTickStep(false); //先关闭自动间隔
    ui->widget->xAxis->setTickStep(30);
    ui->widget->yAxis->setTickStep(50000);

效果图如下:

5.设置可缩放,可拖拽

//设置曲线可拖拽 滚轮放大缩小 图像可选择
    ui->widget->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);

6.显示鼠标点所在坐标值;

这里的坐标值,是以坐标轴为基准的。且坐标信息是附在鼠标处显示的。

//第一,包含鼠标事件头文件
#include <QMouseEvent>

//第二,构建鼠标事件与响应槽函数的连接
/****  鼠标点数据显示  ****/
    connect(ui->widget,&QCustomPlot::mouseMove,this,&MainWindow::myMoveEvent);

//第三,鼠标响应事件
void MainWindow::myMoveEvent(QMouseEvent *event)
{
    double x = event->pos().x();
    double y = event->pos().y();

    double x_ = ui->widget->xAxis->pixelToCoord(x);
    double y_ = ui->widget->yAxis->pixelToCoord(y);

    QString str = QString("x:%1;\ny:%2").arg(QString::number(x_,10,3))
            .arg(QString::number(y_,10,3));
    QToolTip::showText(cursor().pos(),str,ui->widget);
}

效果图如下:

7.支持框选折线图,筛选表中数据的功能

大佬博客的参考链接:https://blog.csdn.net/yxy244/article/details/100547688

步骤:第一,设置曲线图可框选;第二,定义框选信号的槽函数。

下面的代码是槽函数。void MainWindow::on_widget_selectionChangedByUser()是响应曲线图框选动作的。

注意:on_widget_selectionChangedByUser()函数隐含着把selectionChangedByUser()信号和这个函数(作为槽函数)连接起来了。 未来移植的时候,需要改的就是widget这个名字。比如你的UI里,如下图,绘图用控件的名称是Curve就改成on_Curve_selectionChangedByUser()

对于信号与槽机制,搜索一下QMetaObject::connectSlotsByName就会有答案了。隐含的信号与槽函数定义方式。

//选择的数据变化
void MainWindow::on_widget_selectionChangedByUser()
{
    QCustomPlot* customPlot=ui->widget;
    qDebug()<<"ok111";
    //清空listwidget
    //ui->lst_data->clear();
    for(int i=0;i<customPlot->graphCount();i++)
    {
        qDebug()<<"ok1";
        //遍历有被选中的graph
        if(customPlot->graph(i)->selected())
        {
            qDebug()<<"ok2";
            QCPDataSelection selection =customPlot->graph(i)->selection();
            //遍历选中范围
            for(int j=0;j<selection.dataRangeCount();j++)
            {
                qDebug()<<"ok3";
                QCPDataRange dataRange = selection.dataRange(j);
                //遍历数据
                for(int k=dataRange.begin();k<dataRange.end();k++)
                {
                    qDebug()<<"ok4";
                    QString str_key = QString::number(customPlot->graph(i)->data()->at(k)->key);
                    QString str_value = QString::number(customPlot->graph(i)->data()->at(k)->value);
                    QString str_at= QString::number(i);
                    //添加到listwidget
                    qDebug()<<"曲线"<<str_at<<":"<<str_key<<", "<<str_value;
                    //ui->lst_data->addItem("曲线"+str_at+":"+str_key+", "+str_value);
                }
            }
        }
    }
    //滚动到底部
    //ui->lst_data->scrollToBottom();
//    ui->lst_data->scrollToTop();
}

下面的代码是设置曲线图可框选。 

/****  框选曲线图,获得选中数据  ****/
    //数据多选
    ui->widget->graph(0)->setSelectable(QCP::SelectionType::stMultipleDataRanges);
    //选择框模式:无
    ui->widget->setSelectionRectMode(QCP::SelectionRectMode::srmNone);
    //选框黑色虚线
    ui->widget->selectionRect()->setPen(QPen(Qt::black,1,Qt::DashLine));
    ui->widget->selectionRect()->setBrush(QBrush(QColor(0,0,100,50)));
    //设置可框选
    ui->widget->setInteraction(QCP::iRangeDrag,false);//取消拖动
    ui->widget->setSelectionRectMode(QCP::SelectionRectMode::srmSelect);
    //修改多选按键,默认Ctrl。这个函数是设置选中数据的方式
    //customPlot->setMultiSelectModifier(Qt::KeyboardModifier::ControlModifier);
    //滚动缩放、图表可选、多选
    //ui->widget->setInteractions(QCP::iRangeZoom | QCP::iSelectPlottables| QCP::iMultiSelect);//

    qDebug()<<"ok0";
    ui->widget->replot();

8.右键快捷功能

有几个关键类:第一,动作类QAction;第二,菜单类QMenu

动作类就是菜单内的具体操作;菜单分为一级菜单,二级菜单。

菜单类可以添加动作,可以添加下级菜单;

ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);必须添加,响应右键操作。

通过connect给Action定义具体操作。

 /****  设置鼠标右键菜单  ****/
    //主菜单的设置
    //初始化动作
    QAction * curveAction1 = new QAction("btnFirstAction",this);
    QAction * curveAction2 = new QAction("btnSecondAction",this);

    //初始化菜单
    QMenu * curveMenu = new QMenu(this);

    //动作添加到菜单
    curveMenu->addAction(curveAction1);
    curveMenu->addAction(curveAction2);

    //子菜单的设置
    QMenu *childMenu = new QMenu();
    childMenu->setTitle("df");//给子菜单取名字
    QAction *delfile = new QAction(childMenu);
    delfile->setText("删除");
    QAction *addfile = new QAction(childMenu);
    addfile->setText("添加");
    childMenu->addAction(delfile);
    childMenu->addAction(addfile);
    curveMenu->addMenu(childMenu);

    //给动作设置信号槽
    connect( curveAction1, &QAction::triggered, [=]()
    {
        qDebug()<<"I'm btnFirstAction";
    });
    connect( curveAction2, &QAction::triggered, [=]()
    {
        qDebug()<<"I'm btnSecondAction";
    });

    //给曲线图设置上下文菜单策略,鼠标右键点击曲线图时会发送一个void QWidget::customContextMenuRequested(const QPoint &pos)信号
    ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);

    //给信号设置相应的槽函数
    connect(ui->widget,&QLabel::customContextMenuRequested,[=](const QPoint &pos)
    {
        qDebug()<<pos;//参数pos用来传递右键点击时的鼠标的坐标,这个坐标一般是相对于控件左上角而言的
        curveMenu->exec(QCursor::pos());
    });