QCustomPlot 高级扩展与实战案例
QCustomPlot 高级扩展与实战案例
在掌握了 QCustomPlot 的基础使用、样式定制、性能优化之后,我们进入更高阶的应用阶段。本文将带你深入 自定义坐标轴、图表注释、打印集成,并通过两个完整实战案例(实时监控系统、Qt Quick 跨界面展示)展示其工程价值。最后,我们将简要剖析 QCustomPlot 源码结构,为深度定制提供思路。
1. 自定义坐标轴:实现对数轴、时间轴(日期/时间格式)
对数坐标轴(Logarithmic Axis)
QCustomPlot 原生支持对数刻度,只需设置轴类型:
plot->yAxis->setScaleType(QCPAxis::stLogarithmic);
plot->yAxis->setNumberFormat("eb"); // e:科学计数法, b:自动底数
plot->yAxis->setNumberPrecision(0);
plot->yAxis->setRange(1e-2, 1e3); // 注意:对数轴不能包含 0 或负数
⚠️ 注意:数据必须全部为正数,否则会引发渲染异常。
时间轴(日期/时间格式)
QCustomPlot 本身不直接支持 QDateTime
,但可通过 将时间转换为秒/毫秒数值,再自定义刻度标签实现。
// 数据准备:时间戳(秒)
QVector<double> timestamps;
QVector<double> values;
for (int i = 0; i < 100; ++i) {timestamps << QDateTime::currentSecsSinceEpoch() - (100 - i) * 60; // 近100分钟values << qSin(i * 0.1) * 10 + 20;
}// 设置 X 轴为时间轴
plot->xAxis->setTickLabelType(QCPAxis::ltDateTime);
plot->xAxis->setDateTimeFormat("hh:mm\ndd/MM");
plot->xAxis->setDateTimeSpec(Qt::LocalTime); // 或 Qt::UTCplot->addGraph();
plot->graph(0)->setData(timestamps, values);
plot->rescaleAxes();
✅ 提示:
setDateTimeFormat()
支持 Qt 的标准日期格式,如"yyyy-MM-dd hh:mm"
。
2. 图表标注与注释:添加文本标注、箭头、矩形框等辅助元素
QCustomPlot 提供丰富的 QCPAbstractItem
子类用于标注。
添加文本标注
QCPItemText *text = new QCPItemText(plot);
text->setPositionAlignment(Qt::AlignTop | Qt::AlignLeft);
text->position->setType(QCPItemPosition::ptPlotCoords);
text->position->setCoords(x, y); // 图表坐标
text->setText("峰值点");
text->setFont(QFont("Arial", 10, QFont::Bold));
text->setColor(Qt::red);
添加箭头(带文本)
QCPItemLine *arrow = new QCPItemLine(plot);
arrow->start->setType(QCPItemPosition::ptPlotCoords);
arrow->end->setType(QCPItemPosition::ptPlotCoords);
arrow->start->setCoords(x1, y1);
arrow->end->setCoords(x2, y2);
arrow->setHead(QCPLineEnding::esSpikeArrow); // 箭头样式// 可与文本组合使用
添加矩形高亮区域
QCPItemRect *rect = new QCPItemRect(plot);
rect->topLeft->setType(QCPItemPosition::ptPlotCoords);
rect->bottomRight->setType(QCPItemPosition::ptPlotCoords);
rect->topLeft->setCoords(x1, y1);
rect->bottomRight->setCoords(x2, y2);
rect->setBrush(QColor(255, 255, 0, 50)); // 半透明黄色
rect->setPen(Qt::NoPen);
💡 所有
QCPItem
默认随图表缩放/平移自动调整位置。
3. 图表打印功能:将 QCustomPlot 图表集成到 Qt 打印模块
QCustomPlot 内置对 QPrinter
的支持,可直接打印或预览。
打印预览对话框
#include <QPrintPreviewDialog>
#include <QPrinter>void printPlot(QCustomPlot *plot) {QPrinter printer(QPrinter::HighResolution);QPrintPreviewDialog preview(&printer);connect(&preview, &QPrintPreviewDialog::paintRequested, [=](QPrinter *p) {plot->toPainter(p, plot->width(), plot->height());});preview.exec();
}
直接打印
QPrinter printer;
printer.setPageOrientation(QPageLayout::Landscape);
plot->print(&printer);
✅ 优势:打印输出为矢量(PDF)或高分辨率位图,文字清晰不模糊。
4. 实战案例(一):基于 QCustomPlot 的实时数据监控系统
需求
- 实时显示 3 路传感器数据(温度、湿度、压力)
- 支持缩放、平移、重置
- 超限报警(红色高亮)
- 每秒更新 10 次
核心代码结构
class RealTimeMonitor : public QMainWindow {QCustomPlot *plot;QTimer *timer;QVector<double> timeBuffer;QVector<QVector<double>> dataBuffers; // [3][N]public:RealTimeMonitor() {plot = new QCustomPlot(this);setCentralWidget(plot);// 初始化三条曲线for (int i = 0; i < 3; ++i) {plot->addGraph();dataBuffers.append(QVector<double>());}plot->graph(0)->setPen(Qt::red);plot->graph(1)->setPen(Qt::green);plot->graph(2)->setPen(Qt::blue);// 交互plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);// 定时器timer = new QTimer(this);connect(timer, &QTimer::timeout, this, &RealTimeMonitor::updateData);timer->start(100); // 10Hz}void updateData() {double t = QDateTime::currentMSecsSinceEpoch() / 1000.0;timeBuffer.append(t);for (int i = 0; i < 3; ++i) {double val = simulateSensor(i); // 模拟数据dataBuffers[i].append(val);// 超限检查if (val > thresholds[i]) {plot->graph(i)->setPen(QPen(Qt::red, 2));}}// 滚动窗口:保留最近 100 点if (timeBuffer.size() > 100) {timeBuffer.remove(0);for (auto &buf : dataBuffers) buf.remove(0);}// 更新数据for (int i = 0; i < 3; ++i) {plot->graph(i)->setData(timeBuffer, dataBuffers[i]);}plot->xAxis->setRange(t - 10, t + 1); // 动态跟随plot->replot();}
};
✅ 此架构可轻松扩展为多通道工业监控系统。
5. 实战案例(二):QCustomPlot + Qt Quick 的跨界面图表展示
虽然 QCustomPlot 是 QWidget 控件,但可通过 QWidget::createWindowContainer
嵌入 Qt Quick。
步骤
- 创建一个继承
QQuickPaintedItem
的封装类(不推荐,性能差); - 推荐方案:使用
QQuickWidget
或QWidget::createWindowContainer
。
示例:在 QML 中嵌入 QCustomPlot
// main.cpp
QQmlApplicationEngine engine;
QCustomPlot *plot = new QCustomPlot();
// ... 配置 plot// 创建容器
QWidget *container = QWidget::createWindowContainer(plot, nullptr, Qt::Widget);
container->setMinimumSize(600, 400);// 注册为 QML 类型
engine.rootContext()->setContextProperty("plotWidget", container);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
// main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15ApplicationWindow {visible: truewidth: 800; height: 600Column {anchors.centerIn: parentText { text: "QCustomPlot in QML" }// 嵌入 QWidget 容器Rectangle {width: 600; height: 400color: "transparent"Component.onCompleted: {// 通过 C++ 设置的 context property 添加var widget = plotWidgetparent.parent.contentItem.appendChild(widget)}}}
}
⚠️ 注意:此方法在 macOS/Linux 下可能有窗口嵌套问题,建议在纯 QWidget 应用中使用 QCustomPlot,或改用 Qt Charts(原生 QML 支持)。
6. 进阶:QCustomPlot 源码解读(核心渲染逻辑与扩展思路)
源码结构概览
qcustomplot.h/cpp
:核心类定义QCustomPlot
:主控件,负责布局、事件、重绘调度QCPLayer
:图层系统(背景、网格、主图、前景等)QCPAbstractPlottable
:所有可绘图对象的基类(如QCPGraph
,QCPBars
)QCPAxis
/QCPAxisRect
:坐标轴与绘图区域QCPAbstractItem
:标注元素基类
渲染流程
- 调用
replot()
; - 清空各图层;
- 依次调用各 plottable 的
draw()
方法; - 绘制坐标轴、网格、图例;
- 合成最终图像。
扩展思路
- 自定义 Plottable:继承
QCPAbstractPlottable
,重写draw()
和selectTest()
; - 自定义 AxisTicker:实现特殊刻度逻辑(如股票 K 线时间轴);
- 自定义 Item:绘制复杂标注(如带背景的标签、多段箭头)。
示例:自定义 Plottable(简易)
class MyCustomPlot : public QCPAbstractPlottable {
public:MyCustomPlot(QCPAxis *keyAxis, QCPAxis *valueAxis) : QCPAbstractPlottable(keyAxis, valueAxis) {}void draw(QCPPainter *painter) override {// 自定义绘制逻辑painter->setPen(mainPen());painter->drawLine(...);}QRect clipRect() const override { return ...; }void selectTest(...) override { /* 选择检测 */ }
};
📚 建议阅读官方文档中的 “Creating Custom Plottables” 章节。
结语
通过本文,你已掌握 QCustomPlot 的高级扩展能力:
- 灵活定制坐标轴(对数、时间);
- 丰富图表注释提升信息密度;
- 无缝集成打印与报告系统;
- 构建实时监控等工业级应用;
- 在混合界面(Qt Quick)中谨慎使用;
- 具备源码级扩展能力。
QCustomPlot 虽为“轻量级”,但其设计精巧、扩展性强,足以支撑复杂可视化需求。善用其架构,你将能打造出媲美商业软件的专业图表系统。
最后建议:对于新项目,若需深度 QML 集成,可评估
Qt Charts
;若追求性能与定制自由,QCustomPlot 仍是不二之选。