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