QT實時繪圖(利用QtChart,不添加第三方庫)


前言:Qt實時繪圖的需求,一般都是接收某些傳感器的數據並實時顯示到界面上。相關的方法有很多,如利用庫Qwt, QCustomerPlot。由於本人對於Qt不是很熟悉,想着添加第三方庫也需學習新的庫函數,所以就一直在尋找直接利用QtChart實現實時繪圖的方法。由於網上沒有找到相關資料,在此記錄本人的實現方式。

 

1. 官方的實時繪圖實例

  在QT官方例程中,有一個實時繪圖實例。打開QT Creator(本人的QT Creator版本為4.10.2,QT版本為5.12),點擊左邊示例->在搜索框搜索audio->找到實時繪圖例程Audio  Example,如下圖所示(也可以在安裝路徑的qt example中找到):

 

 

 打開該工程,總共有2個cpp文件:widget.cpp和xyseriesiodevice.cpp。其中widget.cpp定義了一個窗口類,該窗口主要用來設置基本的參數,如QChart,QChartView,QValueAxis,QLineseries(這些參數是利用QTChart繪圖的基本配置)。然后xyseriesiodevice.cpp定義了一個IODevice類 。該類的主要作用就是重寫了writeData()函數。並在writeData()函數中更新QLineseries的數據。主要的邏輯就是在Widget的構造函數中調用QAudioInput::start()函數,這樣一旦audio設備有數據,都會發送到IODecive中,通過調用IODevice的writeData()函數,實現QLineseries數據的更新,從而達到實時數據顯示。

 

2. 實現實時繪圖

  從官方的例程中可以看到,為了實現實時繪圖,必須不時更行跟界面綁定的QLineSeries的數據。由於例程利用了QAudioInput的數據接收產生的信號來更新series數據。為了實現自定義的數據刷新,可以利用QTimer定時中斷來更新series數據。因此,改造xyseriesiodevice.cpp函數,不再需要writeData()函數,而是添加定時中斷函數,在該函數中更新數據。改造后的xyseriesiodevice.h和xyseriesiodevice.cpp文件如下:

//xyseriesiodevice.h
class
XYSeriesIODevice : public QObject { Q_OBJECT public: explicit XYSeriesIODevice(QXYSeries *series, QObject *parent = nullptr); static const int sampleCount = 2000; void XYSeriesIODeviceInit(); //用於開啟定時器,連接信號和槽 public slots: void timeoutslot(); //定義定時中斷函數,更行m_series的數據 private: QXYSeries *m_series; QVector<QPointF> m_buffer; float m_time; QTimer m_timer; //定義QTimer,實現定時器更新數據 };
//xyseriesiodevice.cpp

#include "xyseriesiodevice.h" #include <QtCharts/QXYSeries> XYSeriesIODevice::XYSeriesIODevice(QXYSeries *series, QObject *parent) : QObject(parent), m_series(series) { } void XYSeriesIODevice::XYSeriesIODeviceInit() { connect(&m_timer,&QTimer::timeout,this,&XYSeriesIODevice::timeoutslot); if (m_buffer.isEmpty()) { m_buffer.reserve(sampleCount); for (int i = 0; i < sampleCount; ++i) { int x=rand()%10; m_buffer.append(QPointF(x, 0)); } } m_timer.start(100); } //定時中斷中更新數據,即m_series,在此函數中每10個數據更新一次 void XYSeriesIODevice::timeoutslot() { int start=1990; for (int s = 0; s < start; ++s) { m_buffer[s].setX(m_buffer.at(s + 10).x()); m_buffer[s].setY(m_buffer.at(s + 10).y()); } for (int s = 1990; s < sampleCount;s++) { float x=10*cos(0.1*m_time); float y=10*sin(0.1*m_time); //由於沒有數據來源,在此本人設置了圓形軌跡 m_buffer[s].setX(x); m_buffer[s].setY(y); m_time++; } m_series->replace(m_buffer); }


注:為了實現傳感器數據顯示這種實時顯示功能,在定時中斷函數中,設置X坐標為時間,Y坐標為傳感器數據即可,同時還可以調整數據更新的頻率(每收到幾個數據更新一次)。修改后就能看到實時的圓形軌跡,如下圖。

 

 

 

3. 將實時繪圖類整合封裝成一個類

  可以看到,上面的修改比較暴力,主要存在的問題如下:

1. 將一個實時繪圖設置為了兩個類:widget和XYSeriesIODevice,封裝性並不好。

2. 主界面直接就是顯示區,實際應用中,主界面除了實時顯示區,通常還有其他部分(如按鈕,面板之類的)。

實際上,可以發現XYSeriesIODevice類唯一的作用就是定義了QTimer並在定時中斷中實時更新series數據,這個完全可以直接放到widget類實現。主界面直接就是顯示區,是因為我們的項目並沒有默認窗口(一般在創建項目時,都會默認生成一個界面MainWindow),widget類本身就是一個Widge窗口,因此在創建類的同時,創建了窗口。為了解決以上問題,我們從頭建一個項目,將實時繪圖封裝成一個可供調用的類。

主要的操作有以下幾步:

1. 創建一個帶默認窗口的項目,並在界面添加QGraphicsView作為顯示界面(其他可以顯示QChartView的顯示界面均可)。

2. 編寫實時繪圖類RealTimePlot,該類主要需要以下成員變量:

QVector<QPointF> m_buffer:用於保存數據

QTimer m_timer:用於更新數據

QChart m_chart及QChartView m_chartview:用於顯示

QSplineSeries m_series :跟chart關聯的數據series(在此我換成了QSplineSeries,用於畫曲線)。

3. 將顯示界面QGraphicsView跟自定義的實時繪圖類綁定

下面看主要的RealTimePlot類的定義(實際上就是將第2節中的兩個類整合):

//realtimeplot.h

#include <QtCharts>

QT_CHARTS_USE_NAMESPACE


class RealTimePlot:public QGraphicsView
{
    Q_OBJECT
public:
    RealTimePlot(QWidget* parent=nullptr);

    static const int sampleCount=2000;

public slots:
    void timeoutslot();

private:
    QVector<QPointF> m_buffer;
    float m_time;

    QTimer m_timer;

    QChart *m_chart;
    QSplineSeries *m_series ;
};

 

#include "realtimeplot.h"

RealTimePlot::RealTimePlot(QWidget* parent):
    QGraphicsView(parent)
{
    m_chart=new QChart();
    m_series=new QSplineSeries();

    QChartView *chartView = new QChartView(m_chart);
    chartView->setMinimumSize(800, 600);
    m_chart->addSeries(m_series);
    QValueAxis *axisX = new QValueAxis;
    axisX->setRange(-10,10);
    axisX->setLabelFormat("%g");
    axisX->setTitleText("Samples");
    QValueAxis *axisY = new QValueAxis;
    axisY->setRange(-10, 10);
    axisY->setTitleText("Audio level");
    m_chart->addAxis(axisX, Qt::AlignBottom);
    m_series->attachAxis(axisX);
    m_chart->addAxis(axisY, Qt::AlignLeft);
    m_series->attachAxis(axisY);
    m_chart->legend()->hide();
    m_chart->setTitle("Data from the microphone");

    QVBoxLayout *mainLayout = new QVBoxLayout(this);
    mainLayout->addWidget(chartView);

    connect(&m_timer,&QTimer::timeout,this,&RealTimePlot::timeoutslot);

    if (m_buffer.isEmpty()) {
        m_buffer.reserve(sampleCount);
        for (int i = 0; i < sampleCount; ++i)
        {
            int x=rand()%10;
            m_buffer.append(QPointF(x, 0));
        }
    }
    m_timer.start(500);
}


void RealTimePlot::timeoutslot()
{
    int start=1990;

        for (int s = 0; s < start; ++s)
        {
            m_buffer[s].setX(m_buffer.at(s + 10).x());
            m_buffer[s].setY(m_buffer.at(s + 10).y());
        }

    for (int s = 1990; s < sampleCount;s++)
    {
       float x=10*cos(0.1*m_time);
       float y=10*sin(0.1*m_time);
        m_buffer[s].setX(x);
        m_buffer[s].setY(y);
        m_time++;

    }

    m_series->replace(m_buffer);
    //update();
}

 

最后是將GraphicsView和自定義的類綁定,這個就是“提升”(“提升”在本人的理解,就是將一個界面控件(相當於一個類對象)變成自定義帶特殊功能的控件(新的自定義類對象))。由於一個控件只能提升為同類型的類,所以我們編寫的RealTimePlot類繼承了QGraphicsView類。這樣就能將一個GraphicsView控件提升為自定義RealTimePlot類。提升之后,一個GraphicsView控件(假設在MainWindow中叫graphicsView)就相當於函數語句:

ui->graphicsView=new RealTimePlot();

 

這樣,最終的效果如下圖(比較懶,沒貼動圖,實際會是實時顯示的效果):

 

 源碼見:https://github.com/yuanfuaccount/BalanceTrainer/tree/master/PersonalLib/RealTimePlot

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM