Политика конфиденциальностиКонтактыО сайтеОтзывыGitHubDonate
© EVILEG 2015-2018
Рекомендует хостинг
TIMEWEB

Qt/C++ - Урок 031. QCustomPlot - строим график по времени

QCustomPlot, QDateTime, Qt, время, график

Библиотека QCustomPlot имеет возможность построения графиков по шкале времени, что удобно при анализе данных, которые изменяются во времени. Для этого необходимо установить тип подписи к Оси Времени в качестве QCPAxis::ltDateTime. И после этого выставить формат даты и времени. То есть имеется возможность отображать на Оси или дату, или время, или одновременно и то и другое, в зависимости от того, какое форматирование Вы зададите. Правила форматирования для QCustomPlot используются те же, что и для классов QDateTime, QDate, QTime.

Координата времени передаётся в виде числа типа double , которое начинает отсчёт в секундах от времени 1970-01-01T00:00:00 . Что необходимо учитывать при построении графика.

Предлагаю написать приложение, которое будет строить псевдослучайный график дохода и рублях в зависимости от текущей координаты времени. При этом с графиком можно будет взаимодействовать, то есть приближать и удалять его, а также передвигать, но только по горизонтальной Оси. То есть по высоте отображение графика изменяться не будет. Также сделаем возможность изменения формат координат времени в зависимости видимой области графика по Оси Времени. То есть, если видна часть графика меньше, чем за один день, то по оси времени формат подписей будет следующий: hh:mm . В противном случае формат будет "dd MMM yy" .

Строим График

Создаём проект и подключаем в него библиотеку QCustomPlot. Модификации будут подвергаться только файлы mainwindow.h и mainwindow.cpp. Новые файлы добавляться не будут.

mainwindow.h

Объявляем экземпляр класса QCustomPlot, а также экземпляр самого графика. И объявим СЛОТ, в который будет передаваться сигнал об изменении области отображения по Оси Времени.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "qcustomplot.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QCustomPlot *customPlot;    // Объявляем графическое полотно
    QCPGraph *graphic;          // Объявляем график

private slots:
    void slotRangeChanged (const QCPRange &newRange);
};

#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);
    customPlot = new QCustomPlot(); // Инициализируем графическое полотно
    ui->gridLayout->addWidget(customPlot,0,0,1,1);  // Устанавливаем customPlot в окно проложения

    customPlot->setInteraction(QCP::iRangeZoom,true);   // Включаем взаимодействие удаления/приближения
    customPlot->setInteraction(QCP::iRangeDrag, true);  // Включаем взаимодействие перетаскивания графика
    customPlot->axisRect()->setRangeDrag(Qt::Horizontal);   // Включаем перетаскивание только по горизонтальной оси
    customPlot->axisRect()->setRangeZoom(Qt::Horizontal);   // Включаем удаление/приближение только по горизонтальной оси
    customPlot->xAxis->setTickLabelType(QCPAxis::ltDateTime);   // Подпись координат по Оси X в качестве Даты и Времени
    customPlot->xAxis->setDateTimeFormat("hh:mm");  // Устанавливаем формат даты и времени

    // Настраиваем шрифт по осям координат
    customPlot->xAxis->setTickLabelFont(QFont(QFont().family(), 8));
    customPlot->yAxis->setTickLabelFont(QFont(QFont().family(), 8));

    // Автоматическое масштабирование тиков по Оси X
    customPlot->xAxis->setAutoTickStep(true);

    /* Делаем видимыми оси X и Y по верхней и правой границам графика,
     * но отключаем на них тики и подписи координат
     * */
    customPlot->xAxis2->setVisible(true);
    customPlot->yAxis2->setVisible(true);
    customPlot->xAxis2->setTicks(false);
    customPlot->yAxis2->setTicks(false);
    customPlot->xAxis2->setTickLabels(false);
    customPlot->yAxis2->setTickLabels(false);

    customPlot->yAxis->setTickLabelColor(QColor(Qt::red)); // Красный цвет подписей тиков по Оси Y
    customPlot->legend->setVisible(true);   //Включаем Легенду графика
    // Устанавливаем Легенду в левый верхний угол графика
    customPlot->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignLeft|Qt::AlignTop);

    // Инициализируем график и привязываем его к Осям
    graphic = new QCPGraph(customPlot->xAxis, customPlot->yAxis);
    customPlot->addPlottable(graphic);  // Устанавливаем график на полотно
    graphic->setName("Доход, Р");       // Устанавливаем
    graphic->setPen(QPen(QColor(Qt::red))); // Устанавливаем цвет графика
    graphic->setAntialiased(false);         // Отключаем сглаживание, по умолчанию включено
    graphic->setLineStyle(QCPGraph::lsImpulse); // График в виде импульсных тиков

    /* Подключаем сигнал от Оси X об изменении видимого диапазона координат
     * к СЛОТу для переустановки формата времени оси.
     * */
    connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)),
            this, SLOT(slotRangeChanged(QCPRange)));

    // Будем строить график с сегодняшнего дни и текущей секунды в будущее
    double now = QDateTime::currentDateTime().toTime_t();
    // Объявляем вектора времени и доходов
    QVector <double> time(400), income(400);

    srand(15); // Инициализируем генератор псевдослучайных чисел

    // Заполняем график значениями
    for (int i=0; i<400; ++i)
      {
        time[i] = now + 3600*i;
        income[i] = qFabs(income[i-1]) + (i/50.0+1)*(rand()/(double)RAND_MAX-0.5);
      }

    graphic->setData(time, income); // Устанавливаем данные
    customPlot->rescaleAxes();      // Масштабируем график по данным
    customPlot->replot();           // Отрисовываем график
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::slotRangeChanged(const QCPRange &newRange)
{
    /* Если область видимости графика меньше одного дня,
     * то отображаем часы и минуты по Оси X,
     * в противном случае отображаем дату "День Месяц Год"
     * */
    customPlot->xAxis->setDateTimeFormat((newRange.size() <= 86400)? "hh:mm" : "dd MMM yy");
}

Итог

В результате у Вас должен получиться график подобный тому, что показан на ниже следующем рисунке. Отмечу, что при прокручивании колёсика мыши и передвижении графика мышью, сам График будет изменяться только по Оси X, как и было задумано в данном примере.

Демонстрацию работы приложения Вы можете увидеть в видеоуроке.

Архив с проектом

Видеоурок

17 апреля 2017 г. 19:52

Попробовал собрать этот проект, но у меня ошибка ASSERT failure in QVector ::operator[]: "index out of range", file C:\Qt\Qt5.5.1\5.5\mingw492_32\include/QtCore/qvector.h, line 401

17 апреля 2017 г. 20:55

Как правильно вытащить данные из БД, если есть два поля с int и timestamp и вставить в graphic->setData(time, income); // Устанавливаем данные ?

18 апреля 2017 г. 5:30

В Дебаг режиме собирали? Кажется он в дебаге выкидывает ассерт из-за вот этой строки:

income[i] = qFabs(income[i-1]) + (i/50.0+1)*(rand()/(double)RAND_MAX-0.5);

Попробуйте переписать немного иначе:

    for (int i=1; i<400; ++i)
      {
        time[i-1] = now + 3600*i;
        income[i-1] = qFabs(income[i-1]) + (i/50.0+1)*(rand()/(double)RAND_MAX-0.5);
      }
18 апреля 2017 г. 5:33

Вытаскивать нужно обычным QSqlQuery

QSqlQuery query;
query.exec("SELECT column1, cilumn2 FROM table;");

Получите список записей и методами first(), next(), previous(), last() уже пройтись по этому списку, устанавливая данный в график.

20 апреля 2017 г. 5:52

Спасибо. Подскажите еще пожалуйста. У меня есть таблица с доходами.

`id_cash` int(11) NOT NULL, 
`date_cash` date NOT NULL, 
`appointment_cash` int(11) NOT NULL, 
`operation_cash` int(11) NOT NULL, 
`total_cash` int(11) NOT NULL, 
`note_cash` int(11) NOT NULL, 
`documents_cash` int(11) NOT NULL, 
`non_cash` int(11) NOT NULL
Как мне вывести сумму по дням в график?
20 апреля 2017 г. 6:54

total_cash - это сумма за определённую дату, так?

Тогда запрос должен выглядеть примерно так:

SELECT SUM(total_cash) FROM cash_table WHERE date_cash BETWEEN "2017-04-01" AND "2017-04-20" GROUP BY  date_cash

Этот запрос используете с QSqlQuery и в цикле while добавляете данные в график. что-то вроде такого должно получиться:

QSqlQuery query;
query.exec("SELECT SUM(total_cash) FROM cash_table WHERE date_cash BETWEEN "2017-04-01" AND "2017-04-20" GROUP BY  date_cash;)");
while (query.next()) {
    double total_for_day = query.value(0).toDouble();
    // Todo Something
}
20 апреля 2017 г. 8:02

При построении графика используются два параметра, время и доход. Как сопоставить дату с доходом за день?

20 апреля 2017 г. 8:18

Ну а подумать? ))

SELECT date_cash, SUM(total_cash) FROM cash_table WHERE date_cash BETWEEN "2017-04-01" AND "2017-04-20" GROUP BY  date_cash;
20 апреля 2017 г. 16:19

У меня тип данных в поле data_time стоял timestamp, поставил тип data и все ок. Спасибо.

20 апреля 2017 г. 19:30

Проблема с value("date_cash").toDate(); не могу передать в graphic->setData().

21 апреля 2017 г. 3:54
QCPGraph принимает значения времени в типе double, посмотрите внимательно статью, как там инициализируются переменные времени, когда они передаются в график. В статье также про это во втором абзаце сказано.
21 апреля 2017 г. 4:17

Прочитал. Вроде со все разобрался. Спасибо.

М
20 октября 2017 г. 14:04

не могли бы вы выложить архив с рабочей версией скрипта?

20 октября 2017 г. 14:06

После работы поищу, должен где-то быть на винте.

20 октября 2017 г. 21:06

Добавил архив с проектом

М
23 октября 2017 г. 8:44

При компиляции выдает ошибку https://cloud.mail.ru/public/FUxM/b4NFJCb9w

23 октября 2017 г. 9:00

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

М
23 октября 2017 г. 14:02

В режиме выпуск работает

23 октября 2017 г. 14:10

Хорошо. Там в дебаг режиме просто отслеживаются выходы за пределы массива, за счёт чего получается рандомное первоначальное значение. Для примера этого достаточно.

Ну а в своём проекте вам этого не понадобится, когда напишите свою логику построения графика. Поэтому будет работать в Debug режиме с вашей логикой, если только сами ошибок не наделаете.
М
24 октября 2017 г. 7:53

Скажите пожалуйста, как сделать так, что бы при перетаскивании графика, график достраивался? Пока Строиться только то, что попало в окно.

24 октября 2017 г. 7:57

Я давно уже не работал с QCustomPlot, может создадите тему на форуме и приложите свой архив с проектом ? Надо смотреть по факту, что вы сделали.

М
1 ноября 2017 г. 13:00

Скажите пожалуйста, что такое  gridLayout ? В аналогичном проекте выдает ошибку Ui::MainWindow' has no member named 'gridLayout'

1 ноября 2017 г. 20:29

Это объект компоновщика QGridLayout. Данная ошибка говорит о том, что данный объект не был добавлен через графический дизайнер в окно MainWindow

31 мая 2018 г. 8:36

Здравствуйте, у меня есть БД. В нее записываются данные пришедшие с COM-порта и время, когда эти самые данные пришли (QDataTime::currentMSecsSinceEpoch()), пихаю данные DataTime в вектор, но вместо нормальной даты и времени у меня на графике показывается не тот месяц, день и даже год (50 380-год вместо 2018-го). Причем если я меняю QDataTime::currentMSecsSinceEpoch() на currentDateTime().toTime_t() все отображается нормально, но мне нужно время с миллисекундами.

31 мая 2018 г. 8:50

Ну так здесь же указывается установка формата в статье

customPlot->xAxis->setDateTimeFormat((newRange.size() <= 86400)? "hh:mm" : "dd MMM yy");
Добавьте просто соответсвующую установку формата
customPlot->xAxis->setDateTimeFormat("hh:mm:ss.zzz");
31 мая 2018 г. 13:10

Куски кода:

data.append(QDateTime::currentMSecsSinceEpoch());//currentDateTime().toTime_t() //вставляем в БД текущее время в милисекундах с 01.01.1970

Дальше делаем запрос на построение таблицы. Для построения графика достаем из таблицы необходимые данные

for(int i = 0; i < countRow; i++)
    {
       date = ui->tableViewDiagrm->model()->data(//Получаем значения ячеек столбца дата/время
              ui->tableViewDiagrm->model()->index(i, 2)).value<qulonglong>();
        mess = ui->tableViewDiagrm->model()->data(//Получаем значения ячеек столбца данных
               ui->tableViewDiagrm->model()->index(i, 3)).value<QString>();
        //Получение десятичных значений шестнадцатиричной строки
        mesLong = mess.toULongLong(&ok, 16);//Переводим строку в цифры

        vecDateTime.push_back(date);  //Соваем данные в вектор
        vecMessage.push_back(mesLong);//Соваем данные в вектор
    }
    graphic->setData(vecDateTime, vecMessage);//Устанавливаем данные
Настройка оси Х - оси времени
customPlot->xAxis->setTickLabelType(QCPAxis::ltDateTime);//Подпись х как дата/время
customPlot->xAxis->setDateTimeFormat("hh:mm:ss.ms\n dd MM yyyy");
Как результат подпись под осью Х: 20:30:00.200
25 02 50383
если использовать
data.append(QDateTime::currentDateTime().toTime_t());
время и дата отображается корректно, но некорректно отображаются миллисекунды. Что я делаю не так?
31 мая 2018 г. 13:15

Я думаю, что там несколько иной формат объявления милисекуд. Поробуйте так

"hh:mm:ss.zzz\n dd MM yyyy"
31 мая 2018 г. 13:24

Попробовал, но результат тот же. Спасибо за Ваши ответы и за оперативное реагирование. У Вас очень хороший сайт и канал на Utube. Если у меня получится решить проблему, я отпишусь в чем было дело.

31 мая 2018 г. 13:30

Хорошо. Спасибо за отзыв.


И ещё, как вариант попробуйте новую версию QCustomPlot, поскольку эти статьи уже устарели, а я этой библиотекой не занимаюсь.
Но могу дать совет посмотреть в эту часть документации на второй QCustomPlot , там есть информация по установке шаблона отображения времени.
31 мая 2018 г. 13:31

Еще вопрос. На сайте есть такая строчка

QSharedPointer<QCPAxisTickerDateTime> dateTicker(new QCPAxisTickerDateTime);
но в моей подключенной библиотеке нет такого класса QCPAxisTickerDateTime где его можно найти?
31 мая 2018 г. 13:33
Скачайте с того сайта вторую версию QCustomPlot и обновите код своего проекта, чтобы он работал уже с QCustomPlot 2
1 июня 2018 г. 14:05

Проблема решена путём добавления в код следующей строки:

date /= 1000;
т.е.
for(int i = 0; i < countRow; i++)
    {
        date = ui->tableViewDiagrm->model()->data(//Получаем значения ячеек столбца дата/время
               ui->tableViewDiagrm->model()->index(i, 2)).value<qulonglong>();
        date /= 1000;
        mess = ui->tableViewDiagrm->model()->data(//Получаем значения ячеек столбца данных
               ui->tableViewDiagrm->model()->index(i, 3)).value<QString>();
        //Получение десятичных значений шестнадцатиричной строки
        //mess = QString::number(mess.toULongLong(&ok, 16), 10);
        mesLong = mess.toULongLong(&ok, 16);//Переводим строку в цифры

        vecDateTime.push_back(date);  //Соваем данные в вектор
        vecMessage.push_back(mesLong);//Соваем данные в вектор
    }
1 июня 2018 г. 14:56

Ага... ясно... хотя если честно не фига не ясно )) У вас видимо своя какая-то хитрая логика была по отображению миллисекунд.
Впрочем главное, что Вы разобрались ))

1 июня 2018 г. 15:01

Думаю, что по какой-то причине полученное число миллисекунд QCustomPlot воспринимал как секунды

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
m
19 декабря 2018 г. 10:37
maintumanov

Qt - Тест 001. Сигналы и слоты

  • Результат:68баллов,
  • Очки рейтинга-1
ИН
18 декабря 2018 г. 17:37
Игорь Носач

C++ - Тест 003. Условия и циклы

  • Результат:64баллов,
  • Очки рейтинга-1
ИН
18 декабря 2018 г. 17:22
Игорь Носач

C++ - Тест 003. Условия и циклы

  • Результат:35баллов,
  • Очки рейтинга-10
Последние комментарии
V
15 декабря 2018 г. 2:06
Vlad15007

Спасибо большое!Очень помогли!
11 декабря 2018 г. 21:01
Евгений Легоцкой

Не знаю, какой-там конкретно эффект и если честно не хочется fl studio ради того, чтобы посмотреть устанавливать, но из того, что увидел в интернете. Предполагаю, что то, что вы хотите с...
V
11 декабря 2018 г. 19:25
Vlad15007

Подскажите пожалуйста ( я новичок совсем)Можно ли организовать спрайт без этого окошка (как в fl studio fruity dance)?
11 декабря 2018 г. 15:06
Евгений Легоцкой

Что интересно, если написать так from <application_name>.<module_name> import <filename> ,то PyCharm сносит крышу, если разрабатываешь в рамках проекта приложение, ко...
11 декабря 2018 г. 14:52
Илья Чичак

Тут мне тоже есть что сказать=) Сами разрабы советуют импортировать следующим образом: from <application_name> import <module_name> Стоит избегать from . import &l...;
Сейчас обсуждают на форуме
М
19 декабря 2018 г. 8:43
Михаиллл

Здравствуйте.Говорят, если подключить ICU, то в SQLite появится регистронезависемый поиск.Я нашел скомпилированный ICU по этой ссылке https://www.npcglib.org/~stathis/blog/precompiled-...
18 декабря 2018 г. 19:58
Евгений Легоцкой

ну если у вас также будет внешний ключ на Serial, то получается следующая ситуация, Movie добавлен в Serial, а Serial имеет список Movie. То есть вам playlist как таковой вовсе не нужен....
R
18 декабря 2018 г. 12:25
RED_Spider

именно так, проблема в кодировке, а именно в отсутствии шрифтов на сервере, для меня вопрос решился в CentOS 7yum install curl cabextract xorg-x11-font-utils fontconfig всем спасибо за ...
U
18 декабря 2018 г. 10:39
Unreal_man

А вот этот коннект здесь и вовсе не нужен connect(ui->ok3, &QPushButton::clicked, this, &Widget::addToText); А как же без него? ============================== ...
m
17 декабря 2018 г. 19:03
melnik10

Спасибо, попробую!
Присоединяйтесь к нам в социальных сетях

Для зарегистрированных пользователей на сайте присутствует минимальное количество рекламы