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 >= y[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).

Комментарии (5)


  1. chlp
    16.09.2015 12:34
    +9

    В статье про анимированные графики ожидаешь увидеть примеры


    1. antonpv
      16.09.2015 19:57
      +2

      Желательно в виде анимированных gif.


      1. sandyre
        16.09.2015 21:49

        Добавил. Спасибо!


  1. DaylightIsBurning
    17.09.2015 13:53

    Какие преимущества склонили выбор в пользу QCustomPlot, а не Qwt? Qwt ведь, вроде бы, производительней, что для анимаций важно.


    1. sandyre
      17.09.2015 15:10
      +1

      Когда мы выбирали библиотеку для отрисовки графиков, на сайте QCustomPlot'a в примерах был представлен график, который подходил под наше представление того, что мы хотим увидеть в программе. К тому же, он очень прост в понимании и настройке, и на том этапе была важна скорость разработки, а не выбор технологии, так как мы точно представляли, на сколько мы будем «загружать» плот (в нашем случае — ~300 точек на сцену).
      В сети я не нашел perfomance тестов этих двух библиотек, однако идея с тестированием их в полевых условиях действительно интересна!