Графики

QCustomPlot: построение и настройка графиков шаг за шагом

Введение

QCustomPlot — популярная сторонняя (но распространяемая под свободной лицензией) библиотека для построения 2D-графиков в Qt-приложениях, не входящая в сам Qt, но широко используемая благодаря высокой производительности отрисовки, богатой функциональности (множество типов графиков, легенды, интерактивность) и простоте интеграции — это один файл .h/.cpp, который можно добавить непосредственно в проект без сложной системы пакетного управления. В статье разберём построение базового графика с несколькими кривыми.

Концепция

Центральный класс библиотеки — QCustomPlot, наследующий QWidget и встраиваемый в обычную иерархию виджетов как любой другой элемент интерфейса. График строится через добавление одного или нескольких объектов QCPGraph (для линейных графиков), каждый из которых получает данные через setData() с парами X/Y координат. Настройка осей (xAxis, yAxis), подписей, диапазонов и стилей линий выполняется через соответствующие свойства этих объектов, после чего вызов replot() обновляет визуальное отображение.

Пример кода

#include "qcustomplot.h"
#include <QApplication>
#include <QVector>
#include <cmath>

void setupPlot(QCustomPlot *plot)
{
    QVector<double> x(200), ySin(200), yCos(200);
    for (int i = 0; i < 200; ++i) {
        x[i] = i / 10.0;
        ySin[i] = std::sin(x[i]);
        yCos[i] = std::cos(x[i]);
    }

    // Первый график — синус
    QCPGraph *sinGraph = plot->addGraph();
    sinGraph->setData(x, ySin);
    sinGraph->setPen(QPen(Qt::blue, 2));
    sinGraph->setName("sin(x)");

    // Второй график — косинус, на той же системе координат
    QCPGraph *cosGraph = plot->addGraph();
    cosGraph->setData(x, yCos);
    cosGraph->setPen(QPen(Qt::red, 2, Qt::DashLine));
    cosGraph->setName("cos(x)");

    plot->xAxis->setLabel("x");
    plot->yAxis->setLabel("f(x)");
    plot->xAxis->setRange(0, 20);
    plot->yAxis->setRange(-1.5, 1.5);

    // Включаем интерактивность: масштабирование и перемещение мышью
    plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);

    plot->legend->setVisible(true);
    plot->legend->setFont(QFont("Helvetica", 9));

    plot->replot();
}

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QCustomPlot plot;
    plot.resize(640, 480);
    setupPlot(&plot);
    plot.show();

    return app.exec();
}

Пояснения к коду

plot->addGraph() создаёт новый объект QCPGraph, привязанный к осям виджета по умолчанию (xAxis/yAxis) — каждый вызов добавляет ещё одну независимую кривую на тот же график, что демонстрируется здесь двумя графиками (синус и косинус) с разными цветами и стилями линий, заданными через setPen(). Метод setName() задаёт текст, который будет показан в легенде графика, включаемой через plot->legend->setVisible(true).

setInteractions() включает встроенную поддержку взаимодействия мышью — перетаскивание области просмотра (iRangeDrag) и масштабирование колесом мыши (iRangeZoom) работают «из коробки» без необходимости вручную обрабатывать события мыши и пересчитывать видимый диапазон осей. Финальный вызов replot() обязателен — без него изменения данных и настроек не отражаются на экране до следующего события, требующего перерисовки виджета.

Подводные камни

  • Забытый вызов replot() после изменения данных графика в рантайме (например, при добавлении новых точек данных в реальном времени) — график визуально не обновится, пока не произойдёт перерисовка по другой причине (изменение размера окна и так далее), что может выглядеть как «зависший» график при работе с динамически обновляемыми данными.
  • Производительность при очень большом числе точек данных без даунсэмплинга. QCustomPlot эффективен для десятков и сотен тысяч точек, но отрисовка миллионов точек на каждый replot() без предварительного агрегирования (выбор видимого поддиапазона, усреднение соседних точек при сильном уменьшении масштаба) может заметно снижать отзывчивость интерфейса, особенно при частых перерисовках.
  • Интеграция QCustomPlot в проект как единственного файла .h/.cpp без использования системы пакетного менеджера усложняет обновление до новой версии библиотеки — изменения нужно вручную накатывать поверх существующего файла, отслеживая совместимость с уже написанным кодом проекта.
  • QCustomPlot не интегрирован напрямую с QML — это решение специфично для Qt Widgets; для построения графиков в чисто QML/Qt Quick интерфейсах используется либо отдельный модуль Qt Charts (рассмотренный в следующей статье), либо встраивание QCustomPlot-виджета через QQuickWidget/QWidget-контейнер, что добавляет архитектурную сложность смешивания двух UI-технологий.