Pull to refresh

Использование QCustomPlot для создания анимированных графиков

Reading time3 min
Views34K
image

При решении различных аналитических задач может потребоваться real-time построение графиков, где функция зависит от времени. В этой статье я поделюсь своим опытом решения задачи анимирования графиков в Qt, используя QCustomPlot.

Использованный инструментарий


  • Qt Creator 5.5.0
  • QCustomPlot 1.3.1

Кратко о QCustomPlot


Отрисовка графиков происходит после вызова метода replot() (вручную, или при срабатывании события). Для отрисовки данный виджет требует 2 массива данных, содержащих значение аргумента (x) и значение функции в этой точке (y).

Создание графика


Подробнее смотрите в официальном. туториале на сайте QCustomPlot
Для наглядности примера создадим график, аргумент которого будет время в мс.

QVector<double> x(100), y(100);
for(int i = 0; i < 100; i++)
{
   x[i] = 100*i
   y[i] = x[i];
}

Создадим график, который мы в последствии будем анимировать:

customplot->addGraph();

И передадим ему вектора с входными данными:

customplot->graph(0)->setData(x,y);

Установим видимую часть графика равную (0;10000) по x, и (0;10000) по y, чтобы был виден весь график:

customplot->xAxis->setRange(0, 10000);
customplot->yAxis->setRange(0, 10000);

Осталось вызвать replot:

customplot->replot();

Так должен выглядеть наш график


Готово! Осталось лишь непосредственно заставить его последовательно «отрисовываться».

Анимация


Для анимации мы создадим QTimer, который будет сигнализировать о timeout(), и вызывать функцию для отрисовки очередного значения.
Так же нам понадобится глобальная переменная, которая будет хранить данные о прошедшем времени со старта отрисовки, а так же 2 дополнительных массива для хранения x,y, назначение которых будет показано ниже.

int TimeElapsed = 0;
QVector<double> x2, y2; 
...
QTimer* playBackTimer = new QTimer(this); //создание экземпляра таймера
connect(playBackTimer, SIGNAL(timeout()), this, SLOT(PlaybackStep())); //привязка события к функции PlaybackStep()

QTimer работает таким образом, что после вызова метода QTimer->start(int d) по прошествии d миллисекунд он сигнализирует о timeout(), после чего запускается заново до тех пор, пока его не остановить методом QTimer->stop(). Для получения достаточно гладкой картинки, установим d = 50 (20 fps).

playBackTimer->start(50);

А теперь перейдем непосредственно к реализации функции обработки эвента от таймера:

void PlaybackStep()
{
    TimeElapsed+=50; // 50 - частота срабатывания таймера (в мс)
    for(int i = 0; i < x.size(); i++)
    {
            if(TimeElapsed >= x[i])
            {
                x2.push_back(x[i]);
                y2.push_back(y[i]);
                x.pop_front();
                y.pop_front();
                i = 0; // если во временном промежутке несколько подходящих "точек", то после pop_front() мы можем
                // упустить одну. i = 0 запускает заново цикл, чтобы ничего "не потерять"
            }
    }
    customplot->graph(0)->setData(x2, y2);
    //end of playback check
    if(x.size() == 0) playBackTimer->stop();
    //
    customplot->replot();
}

Механизм анимации таков, что на каждом сигнале от таймера берем элементы, меньшие или равные по значению переменной, хранящей количество прошедших миллисекунд.

Так же для того, чтобы каждый раз не пробегать по всему массиву входных данных, после добавления их в новые массивы x2, y2 мы их удаляем при помощи pop_front().

На каждом «кадре» мы обновляем данные графиков вызывая setData(x2,y2), а также запрашиваем перерисовку qcustomplot.

Готовый пример анимации

Данный пример взят из разрабатываемого проекта

Хотелось бы добавить, что, хоть может и показаться, replot() достаточно дорогостоящая функция, чтобы ее вызывать по 20+ раз в секунду, мною были проведены замеры по скорости отрисовки plot. При наличии 10 графиков, по 20-30 значений которых находятся на экране, время выполнения replot() не превышало 1 мс, а время, затраченное на выполнение всей функции PlaybackStep < 10 мс, что теоретически позволяет обновлять график с частотой ~100 фпс.

P.S. Данный пример подходит для реализации «реалтайм» отрисовки, где аргумент функции графика — это время. Однако, этот же механизм можно адаптировать для отрисовки любых других графиков, где зависимости от времени нет.

UPD. Добавлен пример анимированных графиков (gif).
Tags:
Hubs:
+17
Comments5

Articles

Change theme settings