Приложения QT. Построение графика в реальном времени.

В прошлых статьях мы научились строить график QT при помощи библиотеки QWT, теперь возьмем и сделаем так, чтобы он строился в реальном времени. Алгоритм будет такой:

1. Определим два массива x[] и y[] для хранения координат точек кривой графика. В качестве примера заранее заполним массивы данными, но, на самом-то деле, можно записывать туда данные, приходящие, например, из com-порта в реальном времени.
2. Настроим таймер, так, чтобы каждые 25 мс он выдавал сигнал timeout() и присоединим к этому сигналу слот – обработчик timerHandle().
3. В этом обработчике будем брать последнюю не отображенную точку, и передавать ее функции appendGraphPoint().
4. Эта функция будет добавлять точку в массив данных, а также на график. Правда для хранения данных будет использоваться не просто массив, а объект класса CurveData, который мы сами и реализуем.

Итак, самое главное сделали – придумали как все должно работать, осталось реализовать )

Создаем новый проект QT, и сразу добавим в него файлы для реализации класса RealTimePlot. Тут то и будет весь экшн 😉 Для этого идем в меню Файл->Новый файл или проект и далее выбираем «Класс С++»:

Создание нового проекта
Подключаем все, что нам понадобится:

#include <QWidget>
#include <QtGui>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_symbol.h>
#include <qwt_plot_directpainter.h>
#include <qwt_series_data.h>
#include <QPointF>
#include "scrollzoomer.h"
#include "scrollBar.h"

Теперь меняем файл realtimeplot.h:

class RealTimePlot : public QwtPlot
{
    Q_OBJECT
public:
    RealTimePlot();
    ~RealTimePlot();
 
public slots:
    void timerHandle();
 
private:
    QwtPlotCurve *curve;
    QwtPlotDirectPainter *painter;    
    void setData();
    void appendGraphPoint(QPointF point);
    int counter;
    double x[1000];
    double y[1000];
};

Ну, тут все вроде понятно, просто объявляем члены класса RealTimePlot.

Объект curve понадобится непосредственно для рисования кривой,
painter – для дорисовки точек в реальном времени,
counter – тут будем хранить номер точки в массиве данных, которая должна быть добавлена на график следующей.

Добавим реализацию уже упомянутого класса CurveData в файл realtimeplot.cpp:

class CurveData: public QwtArraySeriesData<QPointF>
{
public:
    CurveData()
    {
    }
 
    virtual QRectF boundingRect() const
    {
        if ( d_boundingRect.width() < 0.0 )
            d_boundingRect = qwtBoundingRect( *this );
 
        return d_boundingRect;
    }
 
    inline void appendDataPoint( const QPointF &point )
    {
        d_samples += point;
    }
 
    void clear()
    {
        d_samples.clear();
        d_samples.squeeze();
        d_boundingRect = QRectF( 0.0, 0.0, -1.0, -1.0 );
    }
};

Класс наследует методы и члены класса QwtArraySeriesData, здесь определяются всего лишь 3 метода. Нам в данном примере понадобится только добавление точки – то есть функция appendDataPoint(). В качестве аргумента она принимает как раз таки точку, то есть объект класса QpointF.

Итак, пришло время замутить конструктор для класса RealTimePlot:

RealTimePlot::RealTimePlot()
{
    counter = 0;
    painter = new QwtPlotDirectPainter(this);
    this->setAxisScale(QwtPlot::xBottom, -1, 1);
    this->setAxisScale(QwtPlot::yLeft, 0, 1);
    curve = new QwtPlotCurve("y(x)");
    curve->setStyle(QwtPlotCurve::NoCurve);
    curve->setData( new CurveData() );
    curve->setSymbol( new QwtSymbol(QwtSymbol::Ellipse, Qt::NoBrush,
                                    QPen(Qt::red), QSize(1, 1) ) );
    curvev->attach(this);
    setAutoReplot(false);
    setData();
}

Давайте смотреть, что же тут делается. Объявляем объект painter, он нам будет дорисовывать график в реальном времени.

painter = new QwtPlotDirectPainter(this);

Затем устанавливаем интервалы на осях x и y, которые будут отображаться на графике.

this->setAxisScale(QwtPlot::xBottom, -1, 1);
this->setAxisScale(QwtPlot::yLeft, 0, 1);

Дальше создаем новую кривую (curve), ну и по минимуму настраиваем ее вид, цвет итп.

curve = new QwtPlotCurve("y(x)");
curve->setStyle(QwtPlotCurve::NoCurve);
curve->setData( new CurveData() );
curve->setSymbol( new QwtSymbol(QwtSymbol::Ellipse, Qt::NoBrush,
                                    QPen(Qt::red), QSize(1, 1) ) );

Добавляем кривую на объект RealTimePlot: curve->attach(this);

И, наконец, вызываем функцию setData(), которая заполнит массивы x[] и y[] данными. Кстати, давайте сразу добавим реализацию этой функции:

void RealTimePlot::setData()
{
    const int n = 1000;
    double h = 2.0 / n;
    for(int i = 0; i < n; i++)
    {
        x[i] = -1 + i * h;
        y[i] = qAbs(x[i]);
    }
}

Тут все просто, точно так же мы строили график тут

Не забываем написать деструктор для класса RealTimePlot, который у нас уже объявлен в файле realtimeplot.h:

RealTimePlot::~RealTimePlot()
{
    delete zoomer;
    delete painter;
    delete curve;
}

Теперь поработаем с классом MainWindow. Создадим нужные объекты, а также таймер. Вот полный код файлов mainwindow.h и mainwindow.cpp:
mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include  
#include "realtimeplot.h"
 
namespace Ui {
class MainWindow;
}
 
class MainWindow : public QMainWindow
{
    Q_OBJECT
 
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
 
private:
    Ui::MainWindow *ui;
    RealTimePlot *plot;
    QTimer *timer;
};
 
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
 
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    plot = new RealTimePlot();
    this->setCentralWidget(plot);
    timer = new QTimer(this);
    connect(timer, SIGNAL( timeout() ), plot, SLOT( timerHandle() ) );
    timer->start(25);
}
 
MainWindow::~MainWindow()
{
    delete ui;
}

В конструкторе класса к сигналу таймера timeout() подключаем слот timerHandle() и затем запускаем таймер так, чтобы он сигнализировал нам каждые 25 мс.

Опять идем в файл realtimeplot.cpp и дописываем туда реализацию слота timerHandle():

void RealTimePlot::timerHandle()
{
    QPointF newPoint = QPointF(this-x[this->counter], this->y[this->counter]);
    this->counter++;
    RealTimePlot::appendGraphPoint(newPoint);
}

Тут мы создаем объект newPoint – то есть точку, которая должны быть следующей добавлена на график. Соответственно, увеличиваем счетчик counter. И вызываем метод appendGraphPoint(). Пришло время написать и его 😉

void RealTimePlot::appendGraphPoint(QPointF point)
{
    CurveData *data = static_cast( curve->data() );
    data->appendDataPoint(point);
    painter->drawSeries(curve, 0, data->size() - 1);
}

Здесь мы наконец-то добавляем к данным нашу новую точку и рисуем ее на графике. Вот и все )

Запускаем приложение и видим как медленно, но верно прорисовывается наша функция y=|x|:
График в реальном времени в QT
Можно еще добавить на график интерфейс для масштабирования – как в прошлой статье про графики.

Вот, в общем, мы получили график, рисующийся в реальном времени. Пусть пример довольно простой, но в принципе на его основе можно легко реализовать что-нибудь типа электронного осциллографа, например ) Если возникли какие-либо проблемы или вопросы, пишите в комментарии 😉

Понравилась статья? Поделись с друзьями!

Приложения QT. Построение графика в реальном времени.: 45 комментариев
  1. Отличные статьи! Есть пара вопросов, подскажите пожалста:
    1) как мне организовать прием данных с COM порта?
    2) посоветуйте какую-нибудь литературу/ресурсы в интернете, по которым можно поизучать QT

    p.s. у вас опечатка здесь: QPointF(this-x[this->counter]
    это в обработчике прерывания таймера

    • Спасибо! Стрелка куда-то потерялась)
      По поводу com порта – есть библиотеки специальные, типа как qwt только не для графиков, а для работы с com портом. Я хотел написать заодно и про это, когда графики строил, но что-то руки пока не дошли)
      А литературы очень много по QT (именно книг), но там скорее общие какие-то сведения, лучше под конкретные цели гуглить и смотреть на форумах всяких.

  2. При попытке собрать проект по инструкции появляются три ошибки:
    1) untitled/realtimeplot.h:5: ошибка: expected class-name before ‘{‘ token
    2) untitled/realtimeplot.h:15: ошибка: ‘QwtPlotCurve’ does not name a type
    3) untitled/realtimeplot.h:16: ошибка: ‘QwtPlotDirectPainter’ does not name a type

    Можете что подсказать как собрать правильно, просто совсем не давно начал разбираться в Qt

  3. Видимо что QwtSymbol не нужно в куче объявлять, new ненужен, ну или удалить потом. Также в деструкторе нужно удалить CurveData объект.

  4. А ну да, zoomer удалять тут ненужно, его нет в этой статье (в отличие от предыдущей).

  5. painter удалять тоже не нужно, он удалиться самим QT, ведь родительский объект ему задан через QObject.

  6. Прикрепите архив с проектом, а то если копировать из текста и тыкать самому в файлы, то у меня ошибки вылазят. Ну или скиньте на мыло ifuelen@gmail.com

  7. Здравствуйте. У вас опечатка в “class CurveData: public QwtArraySeriesData
    {
    public:…”
    Правильно “class CurveData: public QwtArraySeriesData
    {
    public:…”
    Из за этого проект не компилируется.
    Только после того как посмотрел ваш скачанный проект понял почему. В нем все правильно.

  8. Удаляется QPointF в стрелках в конце QwtArraySeriesData. Видимо косяк самого сайта.

  9. Здравствуйте.
    Очень понравилась ваша статья. Написано понятно для начинающего. Но у меня все никак не получается собрать проект. Даже полностью скачанный, который тут выложен.
    Библиотека по вашей инструкции собралась нормально. А вот после сборки файла появляется следующая ошибка : C:\QtSDK\qwt\qwt-6.0.1\lib\qwtd.dll:-1: ошибка: LNK1107: invalid or corrupt file: cannot read at 0x2D8.
    Меняла версию библиотеки – не помогло.

  10. QT += core gui

    TARGET = realtimegraph
    TEMPLATE = app

    INCLUDEPATH += C:\QtSDK\qwt\qwt-6.0.1\src

    LIBS +=C:\QtSDK\qwt\qwt-6.0.1\lib\qwtd.dll

    SOURCES += main.cpp\
    mainwindow.cpp \
    realtimeplot.cpp \
    scrollbar.cpp \
    scrollzoomer.cpp

    HEADERS += mainwindow.h \
    realtimeplot.h \
    scrollzoomer.h \
    scrollbar.h

    FORMS += mainwindow.ui

  11. Та ошибка ушла, теперь появилась новая.
    moc_realtimeplot.obj:-1: ошибка: LNK2001: unresolved external symbol “public: static struct QMetaObject const QwtPlot::staticMetaObject” (?staticMetaObject@QwtPlot@@2UQMetaObject@@B)

    moc_scrollzoomer.obj:-1: ошибка: LNK2001: unresolved external symbol “public: static struct QMetaObject const QwtPlotZoomer::staticMetaObject” (?staticMetaObject@QwtPlotZoomer@@2UQMetaObject@@B)

  12. Везде, где есть примеры установки библиотеки, она собрана с помощью mingw. А у меня кьют на стидии 2010 стоит, есть ли какие-нибудь особенности. А то я уже и серийным портом забуксовала

  13. Здравствуйте. Большое спасибо за примеры, пользуюсь Вашими статьями по программированию Qt и STM32. Возможно, у Вас опечатка в строках
    void RealTimePlot::appendGraphPoint(QPointF point)
    CurveData *data = static_cast( curve->data() );
    Строку
    CurveData *data = static_cast (curve->data() );
    заменил на
    CurveData *data = static_cast (curve->data() );
    Иначе компилятор выдавал ошибку.

    • Видимо движок сайта сожрал кавычки, как в комментариях) Спасибо за замечание, сейчас поправлю)

  14. Здравствуйте. У вас опечатка в «class CurveData: public QwtArraySeriesData
    {
    public:…»
    Правильно «class CurveData: public QwtArraySeriesData
    {
    public:…»

    и правильный и не правильный вариант абсолютно одинаковые!!)
    в чем ошибка то??
    а то у меня тоже не компилится

    • Это просто плагин для отображения кода спецсимволы коряво воспринимает почему-то. Я поправил в самой статье, теперь будет работать)

  15. Огромное спасибо за статью!)запустила,все работает,но у меня вопрос: почему-то дорисовывает линию от 1 до 0 после прорисовки графика…никак не пойму что не так…..

    • Скорее всего после прорисовки какие-то некорректные данные продолжают отправляться на прорисовку…

  16. Можно сделать так, что новые точки не будут прорисовываться, если счётчик больше n – достаточно обернуть метод appendGraphPoint в if.

  17. Вопрос. Как сделать так, чтобы график не ПРОрисовывался, а ПЕРЕрисовывался с течением времени?

  18. Так я уже пробовал вызывать метод replot(), но он почему-то всё равно прорисовывает стоячий график, да ещё и совершенно неадекватный.

  19. Здравствуйте! а можно ли этот класс использовать в сечение видео ???допустим есть видео на котором происходят изменения…мне нужно построить график в реальном времени, указав сечения на видео.

  20. Если несложно, не могли бы вы еще раз ссылку на проект скинуть?
    А то со старой ничего скачать уже, видимо, не представляется возможным

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *