В прошлых статьях мы научились строить график при помощи библиотеки 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(), но он почему-то всё равно прорисовывает стоячий график, да ещё и совершенно неадекватный.
Здравствуйте! а можно ли этот класс использовать в сечение видео ???допустим есть видео на котором происходят изменения...мне нужно построить график в реальном времени, указав сечения на видео.
Добрый день!
Не совсем понял задачу..
Если несложно, не могли бы вы еще раз ссылку на проект скинуть?
А то со старой ничего скачать уже, видимо, не представляется возможным