QT串口动态实时显示大量数据波形曲线(二)========“chart绘图与波形观察”

    上篇文章介绍了串口的设置,以下介绍初步画图。

前言

   感觉绘图才是QT等面向对象编程的最好特例。做完之后能感觉到这点是和在单片机或者ARM上逻辑编程最大的区别。

    绘图步骤:

    (1)采用QT自带chart函数绘图。优点:基础,易懂;缺点:麻烦。

    注意:在画图过程中,由于绘图数据包含:三相电压波形(一个坐标系,也就是一张图),三相电流波形,电压电流相位等等,计算下来一共6个图,14条曲线。绘图时间1s间隔,1条曲线100个点。10ms接收40个8位数据进行处理,全部流程走下来,会出现丢点。分析原因:串口接收出现问题和绘图出现问题。经试验确定减小图可以将丢点减小后,所以利用按钮,把6个图减小为4个图,也就是电压显示的时候电流不显示(利用按钮槽函数,很容易实现,代码下面会有显示),丢点情况消失,但是也会有个别点丢失,但是情况出现很少。分析原因:QT中介绍chart函数时,强调能绘出很多点而不卡顿,这点是可以肯定的,利用append也好,利用replace也行,点多是对绘图性能没有影响,或者影响很小。但是图多(不是曲线,不是点数)的情况下,是有影响的,并且影响还不小。

    (2)考虑到绘图数,点数,曲线数的扩展性,采用customplot文件。优点:代码简单;缺点:顶层操作,步骤多,代码没见过就不会用。

    注意:在画图中,参考了网上和customplot自带例程,将坐标系,图和点放入《QT串口大量数据接收并绘图(一)》中的数据处理函数,即1s处理一次(同时串口大概10ms接收一次),此时绘图出现大面积丢点(依旧是6个图,那么多曲线,那么多点)。所以将坐标系,图和点的初始化函数放入ui->setupUi(this)位置下面(差不多是程序的初始化),丢点情况消失。应该是初始化坐标轴,初始化点,和初始化图的程序占用大量时间。改正在数据处理函数中仅仅绘点部分,即可解决点丢失现象。

    (3)此篇中仅仅介绍chart函数绘图,下节介绍customplot函数绘图。

第一部分:准备程序

    (1)pro文件中加入以下程序:

QT       += charts core gui serialport widgets

    其中:core 和 gui 系统文件,和debug还是其他功能有关,具体参看QT的help文件。charts是绘图部分,必须添加。serialport是串口部分,《QT串口大量数据接收并绘图(一)》中利用的串口程序都是基于此。widgets是插件函数,自己编写按钮还有一些其他功能需要用到,加上就对了。

    (2)h文件中加入的include程序

#include                  //主文件头文件
#include                  //串口头文件
#include               //画点状曲线头文件
#include         //画线曲线头文件
#include          //画坐标轴头文件
#include          //画绘图区域头文件
#include                //时间定时器头文件
#include           //手势头文件(鼠标动作)
#include     //绘图屏幕参数头文件
#include      //绘图区域显示头文件
#include            //鼠标动作时间头文件(产生槽函数)

     加入的头文件具体作用已经标出,最好都加上,如果感觉没用可以去掉,如果需要加,最好去查下help文件,看看需要不需要加。

    (3)c文件中加入的include程序

​
#include                      
#include     //端口号索引(用在串口号接收上)
#include                      //链表(用在串口号接收上)

#include           //标签
#include        //图表
#include      //按钮
#include     //
#include   //留言板
#include     //画图
#include    //点
#include       //线
#include        //坐标系

#include               //绘图区域大小变化事件

#include                     //打印参数头文件,最好加上,最好用上,很方便

​

第二部分:头文件初始化

    

using namespace QtCharts;

namespace Ui { class MainWindow; }

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    说明:第一句必须加上,关系到点,线,坐标,图参数的全局化。如果没有这句,只能在某个函数里命名和使用点,线,坐标,图参数,加上子函数,就TM不认识了。这个被搞了好久。在KEIL,MDK或者CCS里面只要声明一个外部全局变量,就可以全部子函数使用此变量,方便又快捷。但是这个必须加上这句,并且第二句也得采用这个形式。自动生成的程序的第二句是有其他东西的,必须删掉才行。开始我采用的是QT自带的例程,结果只能在main函数里面用点,线,坐标,图参数变量,不能放到ui界面,或者其他子函数里面。这个方法就可以定义以下变量了:

public:

//绘图定义变量
    QChartView *chartView_Vs;
    QChart *chart_Vs;
    QLineSeries *m_series_line_Vsua;
    QLineSeries *m_series_line_Vsub;
    QLineSeries *m_series_line_Vsuc;
    QScatterSeries *m_series_point_Vsua;
    QScatterSeries *m_series_point_Vsub;
    QScatterSeries *m_series_point_Vsuc;
    QValueAxis *m_axisX_Vs;
    QValueAxis *m_axisY_Vs;

    命名完之后,在所有子函数里面都能用点,线,坐标,图参数变量。

    注意:可能有其他方法,时间紧迫,此方法可行,也就用此方法了,并且挺简单。

第三部分:定义初始变量

    (1)点放在线上,后面参数可以自己添加,一个x,一个y。

m_series_point_Vsua->append(gfUTCmsBuffer[j], gfVsuAmpBuffer[j]);
m_series_point_Vsub->append(gfUTCmsBuffer[j], gfVsvAmpBuffer[j]);
m_series_point_Vsuc->append(gfUTCmsBuffer[j], gfVswAmpBuffer[j]);

    (2)定义线,h文件里面已经定义,可以简单这样定义,如果h文件里没有定义,需要完整的定义,具体怎么弄网上挺多。

m_series_point_VsuaPha = new QScatterSeries();
m_series_point_VsubPha = new QScatterSeries();
m_series_point_VsucPha = new QScatterSeries();

    (3)定义图,定义标题大小,曲线定义隐藏,最后一句也没查啥意思,别人这样用,我也就用了。

    chart_Vs = new QChart;
    chart_Vs->setTitle("电压三相幅值");
    chart_Vs->setTitleFont(ft);
    chart_Vs->legend()->hide();
    chart_Vs->setAnimationOptions(QChart::AllAnimations);

    (4)线放图上

    chart_Vs->addSeries(m_series_point_Vsua);
    chart_Vs->addSeries(m_series_point_Vsub);
    chart_Vs->addSeries(m_series_point_Vsuc);

    (5)定义绘图区域。第一句:定义绘图区域,并放在开始产生的ui界面上,ui->centralwidget这句话很重要,要不图不能在ui上显示。查了很多例程才找到的方法。当然如果程序在main函数里,就是另一个东西了:&window,也就是直接显示到window的界面里。至于window和ui界面的区别,可以网上找找,最好放ui界面里面,那样方便。第二句:不知道啥意思,也没查,有没有好像没影响。第三句:定义绘图区域的大小,这个要和ui界面的大小配合起来,反正看着好看就行,你喜欢就行。第四句:这句是鼠标动作,也就是放大波形和缩小波形用的,加上这句就能利用鼠标动作事件函数。

    chartView_Vs = new QChartView(ui->centralwidget);
    chartView_Vs->setRenderHint(QPainter::Antialiasing);
    chartView_Vs->setFixedSize(1200,300);
    chartView_Vs->setRubberBand(QChartView::RectangleRubberBand);

    (6)定义坐标系。先定义坐标系。第三句第四句:将坐标系固定在图上。最后三句:将线和坐标系系在一起,可以一起动。

    m_axisX_Vs = new QValueAxis;
    m_axisY_Vs = new QValueAxis;
    chartView_Vs->chart()->addAxis(m_axisX_Vs,Qt::AlignBottom);
    chartView_Vs->chart()->addAxis(m_axisY_Vs,Qt::AlignLeft);

    m_series_point_Vsua->attachAxis(m_axisX_Vs);
    m_series_point_Vsua->attachAxis(m_axisY_Vs);
    m_series_point_Vsub->attachAxis(m_axisX_Vs);

    (7)其他辅助。前三句:设置点的大小,中间三句:设置线的颜色,后四句:设置坐标系的网格。具体效果可以自己试试,属于边边角角的东西,加不加都行。

    m_series_point_Vsua->setMarkerSize(100);
    m_series_point_Vsub->setMarkerSize(100);
    m_series_point_Vsuc->setMarkerSize(100);

    m_series_point_Vsua->setColor(600);
    m_series_point_Vsub->setColor(600);
    m_series_point_Vsuc->setColor(600);

    m_axisX_Vs->setRange(0, 100);
    m_axisX_Vs->setTickCount(11);
    m_axisY_Vs->setRange(-100, 100);
    m_axisY_Vs->setTickCount(5);

第四部分:绘图

    (1) 第三部分的(2)到(7)都是初始化图形。说简单点就是点放线上,线放坐标系上,坐标系放图上,图放开辟的图的区域里面。这个就是面向对象编程的基本逻辑吧。

    (2) 第三部分的(1)即为改变点数据的函数,append之后就会画出新的点,点就会按照上述顺序放在图上,并显示到ui界面上。当然append仅仅是追加点,追加之后前面的点,前面的点不会变,一直加下去。

    (3)坐标系的x和y都要随着时间的变化而变化,需要用到滚屏程序,也就是将坐标系往前挪:scroll(x, y)函数,这个函数其他有点问题,但是系统会告诉用哪个函数,直接改改就行了,好像是Qscroll函数。滚屏的时候需要注意滚动的距离,长了短了都不行,这就需要用到坐标系和屏的一些参数,添加相应头文件就好。

    (4)项目中并没用到滚屏。10ms的40个8位数据中,有1个8位数据是表示时间的,即:10ms,20ms,一直到990ms。1s的时间画一个屏幕,所以我就利用表示时间的参量作为坐标轴的x轴了。每一屏都是10ms到990ms,循环往复。如果需要真实时间作为x轴的话,还是需要(3)中说的滚屏函数的。

    (5)以下介绍ui界面

QT串口动态实时显示大量数据波形曲线(二)========“chart绘图与波形观察”_第1张图片

    制作的ui界面如上图所示。其中电压,电流,频率,频率变化率按钮被屏蔽,串口号和波特率可选,灯为黄色,文字为解析停止。

QT串口动态实时显示大量数据波形曲线(二)========“chart绘图与波形观察”_第2张图片

    工作界面如上图。其中:开始按钮变为停止。电压,电流,频率,频率变化率按钮变为可用。串口号和波特率不可用。文字显示为报头:《QT串口大量数据接收并绘图(一)》中的80007FFF。

QT串口动态实时显示大量数据波形曲线(二)========“chart绘图与波形观察”_第3张图片

工作数据发生故障时界面如上图。其中:数据等变为红灯。文字显示为“解析中......”

QT串口动态实时显示大量数据波形曲线(二)========“chart绘图与波形观察”_第4张图片

    当按下电流和频率变化率按钮时,绘图界面显示按钮对应的实时数据。

    (6)由电流和电压按钮对应的槽函数如下:作用为切换不同的坐标系、实时数据。其本质为隐藏对应绘图区域。

void MainWindow::on_SHOWVOL_clicked()
{
    chartView_Vs->show();
    chart_Vs->show();

    chartView_Is->close();
    chart_Is->close();

    gnVolShowFlag = 1;
    gnCurrShowFlag = 0;
}

void MainWindow::on_SHOWCURR_clicked()
{
    chartView_Vs->close();
    chart_Vs->close();

    chartView_Is->show();
    chart_Is->show();

    gnVolShowFlag = 0;
    gnCurrShowFlag = 1;
}

    函数作用:电压按钮:将电压波形和坐标系展示,电流波形和坐标系关闭。同时加入对应事件FLAG。这个Flag变量最好加上,以便在其他子函数中使用

    (7)如果在一屏中显示,必须清屏,如果不清屏,所有的线都会在最后一个点、第二屏的第一个点产生回折线,挺难看。如果是滚屏的话,这句话就不用了。

m_series_point_Vsua->clear();
m_series_point_Vsub->clear();
m_series_point_Vsuc->clear();

    (8)当ui界面发生大小变化时,其内部的表和按钮什么的也要相应的变化,这个需要用到大小变化的槽函数,需要自己编写

protected:
    virtual void resizeEvent(QResizeEvent *event) override;

    此段为自己编写虚函数,覆盖原有的ui界面大小改变槽函数(中断函数)。加入到h文件里。

void MainWindow::resizeEvent(QResizeEvent *event)
{
    qreal ratioW;
    qreal ratioH;
    ratioW = event->size().width()/1200.0;
    ratioH = event->size().height()/900.0;

    chartView_Vs->setFixedWidth(1200*ratioW);
    chartView_Vs->setFixedHeight(300*ratioH);
}

     加入这个槽函数,当你ui界面大小变化时,自然就会进入此函数,就会执行里面的程序。

    项目设计的初始界面1200乘以900,以此为基准,当大小变化时,其实时的界面大小数据会采集,计算得到比例系数。根据比例系数重新定义图表的大小,就能改变其占用的比例,达到ui界面大小变化,内部图表大小变化的目的。当然如果图表的位置不在原点,还需要将其位置也按照一定的比例进行挪动。使用move语句即可。

    (9)波形观察函数,也就是看波形的时候需要放大或者缩小。具体函数没有看啥意思,关键是脑子不好使,没看懂。但是加上就能用。

    注意:需要加上(5)中的最后一句话。


bool Chart::sceneEvent(QEvent *event)
{
    if (event->type() == QEvent::Gesture)
        return gestureEvent(static_cast(event));
    return QChart::event(event);
}

bool Chart::gestureEvent(QGestureEvent *event)
{
    if (QGesture *gesture = event->gesture(Qt::PanGesture)) {
        QPanGesture *pan = static_cast(gesture);
        QChart::scroll(-(pan->delta().x()), pan->delta().y());
    }

    if (QGesture *gesture = event->gesture(Qt::PinchGesture)) {
        QPinchGesture *pinch = static_cast(gesture);
        if (pinch->changeFlags() & QPinchGesture::ScaleFactorChanged)
            QChart::zoom(pinch->scaleFactor());
    }

    return true;
}

bool ChartView::viewportEvent(QEvent *event)
{
    if (event->type() == QEvent::TouchBegin) {
        // By default touch events are converted to mouse events. So
        // after this event we will get a mouse event also but we want
        // to handle touch events as gestures only. So we need this safeguard
        // to block mouse events that are actually generated from touch.
        m_isTouching = true;

        // Turn off animations when handling gestures they
        // will only slow us down.
        chart()->setAnimationOptions(QChart::NoAnimation);
    }
    return QChartView::viewportEvent(event);
}

void ChartView::mousePressEvent(QMouseEvent *event)
{
    if (m_isTouching)
        return;
    QChartView::mousePressEvent(event);
}

void ChartView::mouseMoveEvent(QMouseEvent *event)
{
    if (m_isTouching)
        return;
    QChartView::mouseMoveEvent(event);
}

void ChartView::mouseReleaseEvent(QMouseEvent *event)
{
    if (m_isTouching)
        m_isTouching = false;

    // Because we disabled animations when touch event was detected
    // we must put them back on.
    chart()->setAnimationOptions(QChart::SeriesAnimations);

    QChartView::mouseReleaseEvent(event);
}

void ChartView::keyPressEvent(QKeyEvent *event)
{
    switch (event->key()) {
    case Qt::Key_Plus:
        chart()->zoomIn();
        break;
    case Qt::Key_Minus:
        chart()->zoomOut();
        break;
//![1]
    case Qt::Key_Left:
        chart()->scroll(-10, 0);
        break;
    case Qt::Key_Right:
        chart()->scroll(10, 0);
        break;
    case Qt::Key_Up:
        chart()->scroll(0, 10);
        break;
    case Qt::Key_Down:
        chart()->scroll(0, -10);
        break;
    default:
        QGraphicsView::keyPressEvent(event);
        break;
    }
}

    直接加上会出现好多错误,是因为h文件里面没有声明函数。这个需要额外加,至于为啥这样加,我就一次一次试验的,这样加好使,什么原因,说不上来,对class的c++编程还是不精通。下面程序加入到h文件中。


class Chart : public QChart
//![1]
{
public:

protected:
    bool sceneEvent(QEvent *event);

private:
    bool gestureEvent(QGestureEvent *event);

private:

};

class ChartView : public QChartView
//![1]
{
public:
    ChartView(QChart *chart, QWidget *parent = 0);

//![2]
protected:
    bool viewportEvent(QEvent *event);
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void keyPressEvent(QKeyEvent *event);
//![2]

private:
    bool m_isTouching;
};

    这个部分添加的时候,直接加到原有的class后面,别加到里面,这个是自己独立的class。加上就能用随意观察波形了。

第五部分:总结

    (1)面向对象的程序像搭积木,放在一起就行了(目前这样的理解,不知道以后会不会更深)。放在一起完成自己想要的功能就行。然后去验证对不对,只要对了,不出现bug,就说明可以了。

    (2)刚接触看到的函数都TM不认识,这是啥,这又是啥。硬着头皮看看,跟着视频或者跟着教程做做,回头看看就感觉很简单了,我也就用一周的时间学习,就开始编写自己的程序了,现在看看还挺简单(或许是理解的太浅,造成的假象,本质的东西还没领悟到,没办法,时间紧迫)。最好能看例程,然后把别人的例程拿过来,直接用,别害怕改,改错了再改回来,总能试验正确的。出现错误的时候根本不知道错误在哪,硬着头皮好好研究研究,慢慢的就行了。

    (3)好好利用里面的help。都是英文,硬着头皮看看,最后你会发现,我去,还能这样,我去,这也太看不懂了,我去,差不多就这样,能用就行。我去,算了,直接去CSDN去找别人写的吧。还是能看就看,基本的还是能看懂的。

    注:由于小伙伴需要源代码的时间不同,登录邮箱界面太多麻烦,所以建立了一个订阅号,如果有问题或者需要源码,可添加订阅号,留言后会发送源代码或者有任何问题可留言,将积极解决提出的问题。

QT串口动态实时显示大量数据波形曲线(二)========“chart绘图与波形观察”_第5张图片

 

你可能感兴趣的:(qt,串口通信,图像处理,数据可视化,面向对象编程)