Qt/C++ - Урок 030. QCustomPlot - быстрый старт в работе с графиками

QCPCurve, QCPItemTracer, QCustomPlot, Qt5, график

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

А теперь ближе к делу. Программа в примере должна выполнять следующий функционал:

  1. Отрисовать один график по нескольким точкам;
  2. Иметь вертикальную передвигаемую линию;
  3. За вертикальной линией будет следовать трассировщик, который будет вставать в ближайшие к точки графика от вертикальной линии.

Структура проекта для QCustomPlot

В структуру проекта входят следующие файлы:

  • QCustomPlotExample.pro - профайл проекта;
  • mainwindow.h - заголовочный файл основного окна приложения;
  • mainwindow.cpp - файл исходных кодов основного окна приложения;
  • main.cpp - основной файл исходных кодов приложения;
  • qcustomplot.h - заголовочный файл библиотеки QCustomPlot;
  • qcustomplot.cpp - файл исходных кодов QCustomPlot.

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

mainwindow.h

В заголовочной файле класса MainWindow подключаем библиотеку QCustomPlot, а также объявляем само полотно, вертикальную линии в качестве объекта класса QCPCurve и трассировщик QCPItemTracer.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

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

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private:
    Ui::MainWindow *ui;
    QCustomPlot *wGraphic;      // Объявляем объект QCustomPlot
    QCPCurve *verticalLine;     // Объявляем объект для вертикальной линии
    QCPItemTracer *tracer;      // Трасировщик по точкам графика

private slots:
    void slotMousePress(QMouseEvent * event);
    void slotMouseMove(QMouseEvent * event);
};

#endif // MAINWINDOW_H

mainwindow.cpp

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

Установка объекта QCustomPlot в окно приложения производится не через графический дизайнер IDE QtCreator , а в исходном коде приложения.

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QApplication>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
  //  this->setGeometry(300,100,640,480);

    // Инициализируем объект полотна для графика ...
    wGraphic = new QCustomPlot();
    ui->gridLayout->addWidget(wGraphic,1,0,1,1); // ... и устанавливаем

    // Инициализируем вертикальную линию
    verticalLine = new QCPCurve(wGraphic->xAxis, wGraphic->yAxis);

    // Подключаем сигналы событий мыши от полотна графика к слотам для их обработки
    connect(wGraphic, &QCustomPlot::mousePress, this, &MainWindow::slotMousePress);
    connect(wGraphic, &QCustomPlot::mouseMove, this, &MainWindow::slotMouseMove);

    // создаём вектора для вертикальной линии
    QVector<double> x(2) , y(2);
        x[0] = 0;
        y[0] = -50;
        x[1] = 0;
        y[1] = 50;

    wGraphic->addPlottable(verticalLine);   // Добавляем линию на полотно
    verticalLine->setName("Vertical");      // Устанавливаем ей наименование
    verticalLine->setData(x, y);            // И устанавливаем координаты

    // создаём вектора для графика
    QVector<double> x1(5) , y1(5);
        x1[0] = -45;
        y1[0] = -43;
        x1[1] = 46;
        y1[1] = 42;
        x1[2] = -25;
        y1[2] = -24;
        x1[3] = -12;
        y1[3] = 10;
        x1[4] = 25;
        y1[4] = 26;

    // Добавляем график на полотно
    wGraphic->addGraph(wGraphic->xAxis, wGraphic->yAxis);
    wGraphic->graph(0)->setData(x1,y1);     // Устанавливаем координаты точек графика

    // Инициализируем трассировщик
    tracer = new QCPItemTracer(wGraphic);
    tracer->setGraph(wGraphic->graph(0));   // Трассировщик будет работать с графиком

    // Подписываем оси координат
    wGraphic->xAxis->setLabel("x");
    wGraphic->yAxis->setLabel("y");

    // Устанавливаем максимальные и минимальные значения координат
    wGraphic->xAxis->setRange(-50,50);
    wGraphic->yAxis->setRange(-50,50);

    // Отрисовываем содержимое полотна
    wGraphic->replot();
}

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

void MainWindow::slotMousePress(QMouseEvent *event)
{
    // Определяем координату X на графике, где был произведён клик мышью
    double coordX = wGraphic->xAxis->pixelToCoord(event->pos().x());

    // Подготавливаем координаты по оси X для переноса вертикальной линии
    QVector<double> x(2), y(2);
    x[0] = coordX;
    y[0] = -50;
    x[1] = coordX;
    y[1] = 50;

    // Устанавливаем новые координаты
    verticalLine->setData(x, y);

    // По координате X клика мыши определим ближайшие координаты для трассировщика
    tracer->setGraphKey(coordX);

    // Выводим координаты точки графика, где установился трассировщик, в lineEdit
    ui->lineEdit->setText("x: " + QString::number(tracer->position->key()) +
                          " y: " + QString::number(tracer->position->value()));
    wGraphic->replot(); // Перерисовываем содержимое полотна графика
}

void MainWindow::slotMouseMove(QMouseEvent *event)
{
    /* Если при передвижении мыши, ей кнопки нажаты,
     * то вызываем отработку координат мыши
     * через слот клика
     * */
    if(QApplication::mouseButtons()) slotMousePress(event);
}

Итог

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

Ссылка на скачивание проекта в zip-архиве: QCustomPlot Example

Видеоурок

Комментарии

23 января 2017 г. 11:30

А почему именно QCustomPlot а не QCharts?

23 января 2017 г. 15:29

Потому, что на момент написания статьи QCharts был только в коммерческой платной версии.

20 октября 2017 г. 13:30

Здравствуйте. Скачал архив, работает. Создал проект и вставил код в 2 файла, ка вы указали - не работает.  В mainwindow.cpp выдает 3 ошибки:

C:\Qt\project\3\mainwindow.cpp:14: ошибка: 'class Ui::MainWindow' has no member named 'gridLayout'
ui->gridLayout->addWidget(wGraphic,1,0,1,1); // ... и устанавливаем
^
C:\Qt\project\3\mainwindow.cpp:30: ошибка: 'class QCustomPlot' has no member named 'addPlottable'
wGraphic->addPlottable(verticalLine);   // Добавляем линию РЅР° полотно
^
C:\Qt\project\3\mainwindow.cpp:91: ошибка: 'class Ui::MainWindow' has no member named 'lineEdit'
ui->lineEdit->setText("x: " + QString::number(tracer->position->key()) +
^
Скажите пожалуйста, как это исправить!
20 октября 2017 г. 13:40

ui->lineEdit и ui->gridLayout - это объекты, которые должны были быть созданы в графическом дизайнере. ui - это объект графического интерфейса. Их у вас просто нет, поработайте с графическим дизайнером, чтобы создать его.


что касается addPlotable, так это отсутсвует инклюд библиотеки, заголовочного файла в mainwindow.h
20 октября 2017 г. 13:43

Но почему вы это не описали? Не могли бы вы описать.


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

Использование дизайнера в Qt Creator и использование ui файлов является распространённой практикой в Qt фреймворке.
Написать отдельную статью про то, что это такое? - может быть.
Описывать в каждой статье основы? - нет. Не о том статья.

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

такая же ошибка с методом addPlottable. Инклуд проблему не решил.

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

Куда инклюд писали?

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

Прописал в заголовочном файле mainwindow.h

Может версия другая? У меня в файле библиотеки вообще нет такого метода.
23 октября 2017 г. 10:03

То есть вы использовали последнюю версию библиотеки с сайта разработчика QCustomPlot?
Да, вы правы.. я посмотрел документацию у разработчика библиотеки. Он выпустил новую версию. И там отсутсвует данный метод.
Там сейчас делатся через QCPGraph, через установку данных через его метод data()

customPlot->graph()->data()->set(timeData);
23 октября 2017 г. 10:09

Спасибо за информацию, а как же теперь пользоваться QCPCurve и другими типами? Краб то заполнить могу информацией, а как добавить её на customPlot?

23 октября 2017 г. 10:11
Краб то заполнить могу информацией
В смысле QCPGraph ?

QCustomPlot имеет метод addGraph
23 октября 2017 г. 10:15

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

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

Или может скачать старую версию библиотеки? Вы не могли бы скинуть файлы старой библиотеки мне на почту?

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

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

А что касается бибилотеки, то скачайте проект из этой статьи . Там в составе проетк есть старая библиотека. Ссылка перед видео.
23 октября 2017 г. 10:25

Спасибо, разберёмся)

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
22 февраля 2018 г. 18:58
Oleg_kgd

C++ - Тест 001. Первая программа и типы данных

  • Результат 66 баллов
  • Очки рейтинга -1
21 февраля 2018 г. 19:18
sentinel

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

  • Результат 78 баллов
  • Очки рейтинга 2
21 февраля 2018 г. 11:32
barilla

C++ - Тест 006. Перечисления

  • Результат 0 баллов
  • Очки рейтинга -10
Последние комментарии
22 февраля 2018 г. 16:42
soz7557

Qt/C++ - Урок 029. Изображение в базе данных в Qt – Сохранение и Восстановление

Hi, could you please show how to delete file from image Blob?  also if the same image exist in Blob then don't over write..

21 февраля 2018 г. 8:37
EVILEG

Qt/C++ - Урок 027. Полиморфизм в Qt на примере геометрических фигур в QGraphicsScene

Добрый день! 1) Эллипс можно реализовать так void Ellipse::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){ painter->setPen(QPen(...

20 февраля 2018 г. 22:10
Log159

Qt/C++ - Урок 027. Полиморфизм в Qt на примере геометрических фигур в QGraphicsScene

Здравствуйте! В программировании новичок и есть пара вопросов. Буду очень благодарен за ответ. Не совсем понимаю как: 1) реализовать подобным образом рисование эллипса(конкре...

18 февраля 2018 г. 14:42
EVILEG

QML - Урок 019. Navigation Drawer в Qt Qml Android

Да, теперь представляю, как то работает. Согласен, ваша правка определённо к месту здесь.

Сейчас обсуждают на форуме
21 февраля 2018 г. 22:19
vitaliy_antipov

Проблема с ComboBox

Спасибо за ответы, есть над чем подумать

21 февраля 2018 г. 13:26
sol11

Qtableviev после сортировки

Спасибо, всё заработало :) Единственное вот тут row на id поменял и всё круто :)) if(id == -1){ model->insertRow(model->rowCount(QModelIndex())); map...

20 февраля 2018 г. 13:18
alex_lip

Разбить один qml файл на несколько составляющих

Да спасибо. Просто после необходимости специфичных названий для файла - стараюсь обращать внимание на любую мелочь.

20 февраля 2018 г. 8:13
EVILEG

Передача файлов в django минуя временные папки django и nginx

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

18 февраля 2018 г. 12:34
EVILEG

QGraphicsView

Добрый день!QGraphicsView - это виджет, а значит, что в качестве парента для него выступает QWidget, а не QObject.То есть из ошибок, которые сразу бросаются в глаза в этом коде, здесь прису...