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

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

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

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

Создаем новый проект Qt, и сразу добавим в него файлы для реализации класса RealTimePlot. Для этого идем в меню “Файл-Новый файл или проект” и далее выбираем “Класс С++”:

Создание нового проекта

Меняем файл 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
{
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 "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.

Можно еще добавить на график интерфейс для масштабирования – как в прошлой статье про графики.

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

Поделиться!

Подписаться
Уведомление о
guest
47 Комментарий
старее
новее большинство голосов
Inline Feedbacks
View all comments
Владимир
Владимир
7 лет назад

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

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

Victor
Victor
7 лет назад

День добрый.
Можете выложить проект на какой либо хостинг?

Victor
Victor
7 лет назад

При попытке собрать проект по инструкции появляются три ошибки:
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

Victor
Victor
Reply to  Aveal
7 лет назад

Спасибо огромное, буду ждать.

Victor
Victor
Reply to  Aveal
7 лет назад

Можешь отправить проект на почту: bazavr@gmail.com

Павел
Павел
Reply to  Aveal
10 месяцев назад

Здравствуйте. Не могли бы Вы выслать проект на почту: gmgfifius@gmail.com
Если он конечно сохранился

Victor
Victor
7 лет назад

P.S. хотя google может и не пропустить…
vit_30@pochta.ru

Иван
Иван
7 лет назад

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

Иван
Иван
7 лет назад

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

Иван
Иван
7 лет назад

Да и this зачем? Можно ж без него …

Иван
Иван
7 лет назад

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

Артур
Артур
7 лет назад

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

Артур
Артур
Reply to  Aveal
7 лет назад

Спасибо большое 🙂

Александр
Александр
6 лет назад

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

Александр
Александр
6 лет назад

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

Ирина
Ирина
6 лет назад

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

Ирина
Ирина
6 лет назад

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

Ирина
Ирина
6 лет назад

Та ошибка ушла, теперь появилась новая.
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)

Ирина
Ирина
6 лет назад

не помогло(

Ирина
Ирина
6 лет назад

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

Сергей
Сергей
6 лет назад

Здравствуйте. Большое спасибо за примеры, пользуюсь Вашими статьями по программированию 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() );
Иначе компилятор выдавал ошибку.

Сергей
Сергей
6 лет назад

Проблемы с комментариями: пропускается фраза в кавычках. static_cast

Сергей
Сергей
6 лет назад

static_cast кавычкиCurveData *кавычки

Олег
Олег
5 лет назад

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

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

Cherkesova
Cherkesova
5 лет назад

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

byulent
byulent
4 лет назад

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

byulent
byulent
4 лет назад

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

byulent
byulent
4 лет назад

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

Evgen
4 лет назад

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

Алексей
Алексей
3 лет назад

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

Присоединяйтесь!

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Август 2020
Пн Вт Ср Чт Пт Сб Вс
 12
3456789
10111213141516
17181920212223
24252627282930
31  

© 2013-2020 MicroTechnics.ru