Evgenii Legotckoi
Evgenii LegotckoiDec. 27, 2015, 9:09 a.m.

Qt/C++ - Lesson 031. QCustomPlot – The build of charts with time

QCustomPlot Library has the ability to charting the scale of time, which is useful in analyzing the data, which change over time. To do this, you need to install a signature style to the time axis as QCPAxis::ltDateTime. And then set the date and time format. That is, it is possible to display on the axes or the date or time, or simultaneously do both, depending on what you specify formatting. Formatting Rules for QCustomPlot used are the same as for QDateTime , QDate , QTime , classes.

Time coordinate is transmitted in the form of a double number that starts the countdown in seconds from time 1970-01-01T00:00:00 . What to consider when plotting.

I propose to write an application that will build a pseudo-random schedule of income and rubles according to the current time coordinate. At the same time the schedule will be to communicate, that is, zoom and remove it, and move, but only on the horizontal axis. That is, the height of the graph display will not change. Also, we make it possible to change the time format in the coordinate dependence of the visible area of ​​the chart for the time axis. That is, if the visible part of the graph in less than one day, the time-axis signature format will be as follows: hh: mm. Otherwise, the format is " dd MMM yy ".

Building a graph

We create a project and connect it to the library QCustomPlot . Modifications will be subject only mainwindow.h and mainwindow.cpp files. New files will not be added.


mainwindow.h

We declare an instance of QCustomPlot , together with a copy of the schedule. And declare the slot in which the signal is transmitted to change the display area on the time axis.

#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->setInteraction(QCP::iRangeZoom,true);   
    customPlot->setInteraction(QCP::iRangeDrag, true);  
    customPlot->axisRect()->setRangeDrag(Qt::Horizontal);   // Enable only drag along the horizontal axis
    customPlot->axisRect()->setRangeZoom(Qt::Horizontal);   // Enable zoom only on the horizontal axis
    customPlot->xAxis->setTickLabelType(QCPAxis::ltDateTime);   // Labl coordinates along the X axis as the Date and Time
    customPlot->xAxis->setDateTimeFormat("hh:mm");  // Set the date and time format

    // Customizable fonts on the axes
    customPlot->xAxis->setTickLabelFont(QFont(QFont().family(), 8));
    customPlot->yAxis->setTickLabelFont(QFont(QFont().family(), 8));

    // Automatic scaling ticks on the X-axis
    customPlot->xAxis->setAutoTickStep(true);

    /* We are making visible the X-axis and Y on the top and right edges of the graph, 
     * but disable them tick and labels coordinates
     * */
    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));  
    customPlot->legend->setVisible(true);   // Enable Legend 
    // Set the legend in the upper left corner of the chart
    customPlot->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignLeft|Qt::AlignTop);

    // Initialize the chart and bind it to the axes
    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); // Charts in a pulse ticks view

    connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)),
            this, SLOT(slotRangeChanged(QCPRange)));

    // We will build schedule with today's day and the current second in a future
    double now = QDateTime::currentDateTime().toTime_t();
    // We declare a vector of time and income
    QVector <double> time(400), income(400);

    srand(15); // Initialize the random number generator

    // ЗFill in the values of the graph
    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)
{
    /* If a scope chart is less than one day, 
     * then display the hours and minutes in the Axis X, 
     * otherwise display the date "Month Day Year"
     * */
    customPlot->xAxis->setDateTimeFormat((newRange.size() <= 86400)? "hh:mm" : "dd MMM yy");
}

Result

As a result, you should get a graph similar to that shown in the following figure. Note that when you scroll the mouse wheel and moving the mouse the chart, graph itself will only be changed on the Axes X, as was intended in this example.

Download Project

Video

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

Юрий
  • April 17, 2017, 3:52 p.m.

Попробовал собрать этот проект, но у меня ошибка 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

Юрий
  • April 17, 2017, 4:55 p.m.

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

Evgenii Legotckoi
  • April 18, 2017, 1:30 a.m.

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

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);
      }
Evgenii Legotckoi
  • April 18, 2017, 1:33 a.m.

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

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

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

Юрий
  • April 20, 2017, 1:52 a.m.

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

`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
Как мне вывести сумму по дням в график?
Evgenii Legotckoi
  • April 20, 2017, 2:54 a.m.

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
}
Юрий
  • April 20, 2017, 4:02 a.m.

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

Evgenii Legotckoi
  • April 20, 2017, 4:18 a.m.

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

SELECT date_cash, SUM(total_cash) FROM cash_table WHERE date_cash BETWEEN "2017-04-01" AND "2017-04-20" GROUP BY  date_cash;
Юрий
  • April 20, 2017, 12:19 p.m.

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

Юрий
  • April 20, 2017, 3:30 p.m.

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

Evgenii Legotckoi
  • April 20, 2017, 11:54 p.m.
QCPGraph принимает значения времени в типе double, посмотрите внимательно статью, как там инициализируются переменные времени, когда они передаются в график. В статье также про это во втором абзаце сказано.
Юрий
  • April 21, 2017, 12:17 a.m.

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

М
  • Oct. 20, 2017, 10:04 a.m.

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

Evgenii Legotckoi
  • Oct. 20, 2017, 10:06 a.m.

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

Evgenii Legotckoi
  • Oct. 20, 2017, 5:06 p.m.

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

М
  • Oct. 23, 2017, 4:44 a.m.

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

Evgenii Legotckoi
  • Oct. 23, 2017, 5 a.m.

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

М
  • Oct. 23, 2017, 10:02 a.m.

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

Evgenii Legotckoi
  • Oct. 23, 2017, 10:10 a.m.

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

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

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

Evgenii Legotckoi
  • Oct. 24, 2017, 3:57 a.m.

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

М
  • Nov. 1, 2017, 9 a.m.

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

Evgenii Legotckoi
  • Nov. 1, 2017, 4:29 p.m.

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

pasagir
  • May 31, 2018, 4:36 a.m.

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

Evgenii Legotckoi
  • May 31, 2018, 4:50 a.m.

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

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

Куски кода:

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());
время и дата отображается корректно, но некорректно отображаются миллисекунды. Что я делаю не так?
Evgenii Legotckoi
  • May 31, 2018, 9:15 a.m.

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

"hh:mm:ss.zzz\n dd MM yyyy"
pasagir
  • May 31, 2018, 9:24 a.m.

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

Evgenii Legotckoi
  • May 31, 2018, 9:30 a.m.

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


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

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

QSharedPointer<QCPAxisTickerDateTime> dateTicker(new QCPAxisTickerDateTime);
но в моей подключенной библиотеке нет такого класса QCPAxisTickerDateTime где его можно найти?
Evgenii Legotckoi
  • May 31, 2018, 9:33 a.m.
Скачайте с того сайта вторую версию QCustomPlot и обновите код своего проекта, чтобы он работал уже с QCustomPlot 2
pasagir
  • June 1, 2018, 10:05 a.m.

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

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);//Соваем данные в вектор
    }
Evgenii Legotckoi
  • June 1, 2018, 10:56 a.m.

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

pasagir
  • June 1, 2018, 11:01 a.m.

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

i
  • June 13, 2019, 10:09 a.m.
  • (edited)

Здравствайте! Подскажите, пожалуйста:

customPlot->xAxis2->setTickLabels(true); //Здесь включается отображение данных на оси xAxis2.

а можно как-то продублировать информацию customPlot->xAxis на ось customPlot->xAxis2.

Хотелось бы вверху отображать только даты. А на нижней оси только часы:мин:сек.

Evgenii Legotckoi
  • June 16, 2019, 4:21 p.m.

Добрый день.

Ну точно также добавляете ту же самую информацию на ось xAxis2, только добавляете другое форматирование

customPlot->xAxis2->setDateTimeFormat("hh:mm");

если я правильно понял ваш вопрос, конечно

i
  • June 17, 2019, 2:10 a.m.
  • (edited)

Только по осям xAxis2, уAxis2 значения начинаются с 0. Почему-то xAxis2 и xAxis не синхронизированы по данным. Ну и QCustomPlot последний.

Comments

Only authorized users can post comments.
Please, Log in or Sign up
г
  • ги
  • April 23, 2024, 3:51 p.m.

C++ - Test 005. Structures and Classes

  • Result:41points,
  • Rating points-8
l
  • laei
  • April 23, 2024, 9:19 a.m.

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:10points,
  • Rating points-10
l
  • laei
  • April 23, 2024, 9:17 a.m.

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

  • Result:50points,
  • Rating points-4
Last comments
k
kmssrFeb. 8, 2024, 6:43 p.m.
Qt Linux - Lesson 001. Autorun Qt application under Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lesson 007. Working with ICMP Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVADec. 25, 2023, 10:30 a.m.
Boost - static linking in CMake project under Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJoDec. 25, 2023, 8:38 a.m.
Boost - static linking in CMake project under Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
GvozdikDec. 18, 2023, 9:01 p.m.
Qt/C++ - Lesson 056. Connecting the Boost library in Qt for MinGW and MSVC compilers Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Now discuss on the forum
G
GarApril 22, 2024, 5:46 a.m.
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil AcademicsApril 20, 2024, 7:45 a.m.
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasovApril 14, 2024, 6:41 a.m.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел ДорофеевApril 14, 2024, 2:35 a.m.
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrexApril 4, 2024, 4:47 a.m.
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…

Follow us in social networks