В прошлых статьях мы научились строить график при помощи библиотеки 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|:
Можно еще добавить на график интерфейс для масштабирования – как в прошлой статье про графики.
В общем, мы получили график, отрисовывающийся в реальном времени. Пусть пример довольно простой, но в принципе на его основе можно легко реализовать что-нибудь вроде электронного осциллографа, например ) Если возникли какие-либо проблемы или вопросы, пишите в комментарии, буду рад помочь!





Отличные статьи! Есть пара вопросов, подскажите пожалста:
1) как мне организовать прием данных с COM порта?
2) посоветуйте какую-нибудь литературу/ресурсы в интернете, по которым можно поизучать QT
p.s. у вас опечатка здесь: QPointF(this-x[this->counter]
это в обработчике прерывания таймера
Спасибо! Стрелка куда-то потерялась)
По поводу com порта - есть библиотеки специальные, типа как qwt только не для графиков, а для работы с com портом. Я хотел написать заодно и про это, когда графики строил, но что-то руки пока не дошли)
А литературы очень много по QT (именно книг), но там скорее общие какие-то сведения, лучше под конкретные цели гуглить и смотреть на форумах всяких.
День добрый.
Можете выложить проект на какой либо хостинг?
При попытке собрать проект по инструкции появляются три ошибки:
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
Добрый день!
Я как вернусь с работы, обязательно сразу же выложу проект, можно будет попробовать собрать его, у меня он сейчас вроде нормально собрался
Спасибо огромное, буду ждать.
Можешь отправить проект на почту: bazavr@gmail.com
Здравствуйте. Не могли бы Вы выслать проект на почту: gmgfifius@gmail.com
Если он конечно сохранился
Добрый вечер!
К сожалению, проект не сохранился...
P.S. хотя google может и не пропустить...
vit_30@pochta.ru
Прошу прощения, что поздно, только вернулся домой) Отправил на почту проект
Видимо что QwtSymbol не нужно в куче объявлять, new ненужен, ну или удалить потом. Также в деструкторе нужно удалить CurveData объект.
А ну да, zoomer удалять тут ненужно, его нет в этой статье (в отличие от предыдущей).
Да и this зачем? Можно ж без него ...
painter удалять тоже не нужно, он удалиться самим QT, ведь родительский объект ему задан через QObject.
Прикрепите архив с проектом, а то если копировать из текста и тыкать самому в файлы, то у меня ошибки вылазят. Ну или скиньте на мыло ifuelen@gmail.com
Просто тут в тексте только суть, неполный текст файлов, проект все забываю прикрепить. Скину на почту и если не забуду завтра прикреплю к статье
Что-то гуглопочта заругалась, вот проект - http://rusfolder.com/36120755
Спасибо большое 🙂
Здравствуйте. У вас опечатка в "class CurveData: public QwtArraySeriesData
{
public:..."
Правильно "class CurveData: public QwtArraySeriesData
{
public:..."
Из за этого проект не компилируется.
Только после того как посмотрел ваш скачанный проект понял почему. В нем все правильно.
Удаляется QPointF в стрелках в конце QwtArraySeriesData. Видимо косяк самого сайта.
Спасибо, поправил ) WordPress очень любит попортить код на сайте...
Здравствуйте.
Очень понравилась ваша статья. Написано понятно для начинающего. Но у меня все никак не получается собрать проект. Даже полностью скачанный, который тут выложен.
Библиотека по вашей инструкции собралась нормально. А вот после сборки файла появляется следующая ошибка : C:\QtSDK\qwt\qwt-6.0.1\lib\qwtd.dll:-1: ошибка: LNK1107: invalid or corrupt file: cannot read at 0x2D8.
Меняла версию библиотеки - не помогло.
Добрый день )
А что у вас в файле .pro?
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
Можно попробовать заменить LIBS +=C:\QtSDK\qwt\qwt-6.0.1\lib\qwtd.dll на LIBS +=-LC:\QtSDK\qwt\qwt-6.0.1\lib -lqwt
Та ошибка ушла, теперь появилась новая.
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)
Надо пересобрать, можно удалить папку Debug проекта и пересобрать
не помогло(
Похоже, что библиотека не подцепилась к проекту все-таки..
Везде, где есть примеры установки библиотеки, она собрана с помощью mingw. А у меня кьют на стидии 2010 стоит, есть ли какие-нибудь особенности. А то я уже и серийным портом забуксовала
Оу..я студией вообще не пользуюсь, недолюбливаю ее)
Здравствуйте. Большое спасибо за примеры, пользуюсь Вашими статьями по программированию 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() );
Иначе компилятор выдавал ошибку.
Проблемы с комментариями: пропускается фраза в кавычках. static_cast
static_cast кавычкиCurveData *кавычки
Видимо движок сайта сожрал кавычки, как в комментариях) Спасибо за замечание, сейчас поправлю)
Здравствуйте. У вас опечатка в «class CurveData: public QwtArraySeriesData
{
public:…»
Правильно «class CurveData: public QwtArraySeriesData
{
public:…»
и правильный и не правильный вариант абсолютно одинаковые!!)
в чем ошибка то??
а то у меня тоже не компилится
Это просто плагин для отображения кода спецсимволы коряво воспринимает почему-то. Я поправил в самой статье, теперь будет работать)
Огромное спасибо за статью!)запустила,все работает,но у меня вопрос: почему-то дорисовывает линию от 1 до 0 после прорисовки графика...никак не пойму что не так.....
Скорее всего после прорисовки какие-то некорректные данные продолжают отправляться на прорисовку...
Можно сделать так, что новые точки не будут прорисовываться, если счётчик больше n - достаточно обернуть метод appendGraphPoint в if.
Вопрос. Как сделать так, чтобы график не ПРОрисовывался, а ПЕРЕрисовывался с течением времени?
Удалять предыдущий и рисовать новый
Так я уже пробовал вызывать метод replot(), но он почему-то всё равно прорисовывает стоячий график, да ещё и совершенно неадекватный.
Здравствуйте! а можно ли этот класс использовать в сечение видео ???допустим есть видео на котором происходят изменения...мне нужно построить график в реальном времени, указав сечения на видео.
Добрый день!
Не совсем понял задачу..
Если несложно, не могли бы вы еще раз ссылку на проект скинуть?
А то со старой ничего скачать уже, видимо, не представляется возможным