Qt 工业软件 GPLv3 污染危机:QCustomPlot → Qt Charts 迁移实战
工业软件要商业化,许可证合规是必经之路。QCustomPlot 的 GPLv3 许可给闭源产品埋下了隐患。本文详述 PLCMonitor 如何从 QCustomPlot 迁移到 Qt Charts(LGPLv3),消除 GPL 污染,以及迁移过程中的 API 映射和坑点。
免责声明:本文仅描述 PLCMonitor 自身情况,不构成法律建议。以你最终采购的法律意见为准。
本文目录: ①GPLv3 风险 ②API 映射 ③实施步骤 ④核心代码 Diff ⑤风险点 ⑥结语
① GPLv3 风险:为什么必须迁移?
1.1 什么是 GPL 污染
GPL(General Public License)是最严格的开源许可证之一。核心条款:
衍生作品必须同样以 GPL 发布
这意味着:如果你的软件链接了 GPLv3 的库,你的整个软件都必须开源。
对于商业工业软件,这是不可接受的。
1.2 QCustomPlot 的问题
QCustomPlot 是一个优秀的 2D 绘图库,功能强大、API 简洁。但它采用 GPLv3 许可:
QCustomPlot – a modern Qt plotting widget
Copyright (C) 2011-2024 Emil Frey
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License.
一旦链接 QCustomPlot,PLCMonitor 就必须:
1.3 影响范围评估
迁移前,先评估影响范围:
直接依赖文件:
├── src/ui/widgets/chartpanel.h ← #include "qcustomplot.h"
├── src/ui/widgets/chartpanel.cpp ← 全部 QCustomPlot API 调用
├── src/app/CMakeLists.txt ← 编译 QCustomPlot 源码
├── CMakeLists.txt(顶层) ← 需要添加 Charts 组件
└── src/common/qcustomplot.h/.cpp ← GPL 源码,需删除
间接影响(无需修改):
├── src/ui/models/ChartManager ← 纯数据管理
├── src/ui/models/TrendManager ← 纯数据管理
└── src/app/mainwindow ← 只通过 customChart() 调用截图
结论:需要修改的核心文件只有 4 个,删除 2 个文件。
1.4 工作量对比
| 删除 QCustomPlot | -3500 | qcustomplot.h/.cpp + chartpanel 中相关代码 |
| 新增 Qt Charts | +213 | 头文件替换 + setupUI + 数据更新 |
| CMakeLists 修改 | ±10 | 组件声明 + 链接库 |
| 净效果 | -3287 | 代码量减少,许可证风险归零 |
② API 映射:QCustomPlot → Qt Charts
核心类型映射
| QCustomPlot | QChartView + QChart | Chart 是数据模型,View 是视图组件 |
| QCPGraph | QLineSeries | 线图系列 |
| QCPAxis | QValueAxis / QDateTimeAxis | X 轴建议用 DateTime |
| QCPAxisRect | 不再需要 | QChartView 自动管理布局 |
| QCPGrid | QValueAxis::setGridLineVisible() | 网格线通过 Axis 控制 |
| legend | QChart::legend() | 图例由 Chart 管理 |
关键方法映射
| plot->addGraph(xAxis, yAxis) | series = new QLineSeries; chart->addSeries(series) |
| graph->setPen(pen) | series->setPen(pen) |
| graph->setData(x, y) | series->replace(x.begin(), x.end(), y.begin(), y.end()) |
| graph->data()->clear() | series->clear() |
| plot->xAxis->setRange(min, max) | axisX->setRange(min, max) |
| plot->replot() | chart->update()(通常自动重绘) |
| plot->savePdf(path) | chart->savePdf(path) |
轴类型选择
| 实时模式 X 轴 | QCPAxisTickerDateTime | QDateTimeAxis |
| 历史模式 X 轴 | 绝对时间戳 | QDateTimeAxis |
| 左 Y 轴 | QValueAxis | QValueAxis |
| 右 Y 轴 | QValueAxis (yAxis2) | QValueAxis(第二个实例) |
③ 实施步骤(精简版)
Step 1:修改 CMakeLists.txt
顶层 CMakeLists.txt 添加 Charts 组件:
find_package(Qt6 REQUIRED
COMPONENTS Core Widgets Sql Svg Network PrintSupport Concurrent Pdf Charts
)
src/app/CMakeLists.txt 删除 QCustomPlot 编译段,添加 Qt6::Charts:
# 删除 QCustomPlot 源码编译
# set(QCUSTOMPLOT_SRCS …)
# if(MINGW) … endif()
qt_add_executable(${PROJECT_NAME}
${PLCMonitor_SRCS}
${SOURCE_FILES}
${UI_FILES}
)
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt6::Widgets Qt6::Sql Qt6::Core Qt6::Network
Qt6::PrintSupport Qt6::Pdf
Qt6::Charts # 新增
)
Step 2:修改 chartpanel.h
// 删除
#include "qcustomplot.h"
// 新增
#include <QChartView>
#include <QChart>
#include <QLineSeries>
#include <QValueAxis>
#include <QDateTimeAxis>
成员变量替换:
// 删除
QCustomPlot *m_plot = nullptr;
QCPAxis *m_axisRight = nullptr;
QMap<QString, QCPGraph*> m_graphs;
// 新增
QChartView *m_plotView = nullptr;
QChart *m_chart = nullptr;
QDateTimeAxis *m_axisX = nullptr;
QValueAxis *m_axisLeft = nullptr;
QValueAxis *m_axisRight = nullptr;
QMap<QString, QLineSeries*> m_series;
Step 3:删除 QCustomPlot 源码
rm src/common/qcustomplot.h
rm src/common/qcustomplot.cpp
Step 4:更新许可证文件
– | QCustomPlot | 2.1.0 | GPLv3 | 趋势图表绘制 | qcustomplot.com |
+ | Qt Charts | 6.11.1 | LGPLv3 | 趋势图表绘制(Qt 原生) | qt.io |
④ 核心代码 Diff
Diff 1:截图实现
旧版 QCustomPlot 有原生 savePng 接口,支持 KeepAspectRatio 等参数:
// 旧:QCustomPlot
customPlot()->savePng(path, 0, 0, 2.0);
customPlot()->savePdf(path);
新版 Qt Charts 的 QChart 完全不提供 savePng() 方法,正确做法是需要通过 QChartView::grab() 截取像素图间接实现:
// 新:Qt Charts(项目实际实现)
const QPixmap pix = m_plotView->grab();
pix.save(path, "PNG");
PDF 导出仍然可以用 chart->savePdf(path)。
Diff 2:X 轴时间戳——迁移第一大坑
QDateTimeAxis 要求 X 值必须是毫秒级 epoch,传秒级会显示成 1970-01-01 或空白。
// ✗ 错误:除以 1000 得到秒级,QDateTimeAxis 无法正确解析
series->append(QPointF(QDateTime::fromSecsSinceEpoch(t).toMSecsSinceEpoch() / 1000.0, value));
// ✓ 正确:直接使用毫秒级 epoch
series->append(QPointF(QDateTime::fromSecsSinceEpoch(t).toMSecsSinceEpoch(), value));
QDateTime::fromSecsSinceEpoch(t) 已将秒转为 QDateTime,再调用 .toMSecsSinceEpoch() 得到毫秒级 epoch,不需要再除以 1000。这是迁移过程中的第一大坑。
Diff 3:双 Y 轴绑定
// 右轴未绑定时标题不绘制
m_axisRight->setVisible(false);
// 切换 series 绑定的轴时,务必先 detach 再 attach
series->detachAxis(m_axisRight);
series->attachAxis(m_axisLeft);
Diff 4:setupUI() 图表初始化
// QCustomPlot 旧代码
m_plot = new QCustomPlot();
m_plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
m_plot->axisRect()->setupFullAxesBox(true);
m_plot->xAxis->grid()->setVisible(true);
m_plot->yAxis->grid()->setVisible(true);
m_plot->legend->setVisible(false);
m_axisRight = m_plot->yAxis2;
// Qt Charts 新代码
m_chart = new QChart();
m_chart->setTitle("");
m_chart->legend()->setVisible(false);
m_chart->setTheme(QChart::ChartThemeLight);
m_axisX = new QDateTimeAxis();
m_axisX->setFormat("HH:mm:ss");
m_axisX->setGridLineVisible(true);
m_chart->addAxis(m_axisX, Qt::AlignBottom);
m_axisLeft = new QValueAxis();
m_axisLeft->setRange(–1, 1);
m_axisLeft->setGridLineVisible(true);
m_chart->addAxis(m_axisLeft, Qt::AlignLeft);
m_axisRight = new QValueAxis();
m_axisRight->setRange(–1, 1);
m_axisRight->setGridLineVisible(false);
m_chart->addAxis(m_axisRight, Qt::AlignRight);
m_plotView = new QChartView(m_chart);
m_plotView->setRenderHint(QPainter::Antialiasing);
m_plotView->setInteractive(true);
⑤ 风险点与注意事项
5.1 双 Y 轴标签
Qt Charts 的右轴标签通过 setTitle() 设置,与 QCustomPlot 的 setLabel() 等效。右轴绑定 series 后标题才会显示。切换左右轴绑定时,确保先 detachAxis 再 attachAxis。
5.2 历史模式 vs 实时模式
历史模式使用绝对时间戳(秒级),通过 QDateTimeAxis::setRange() 传入格式化后的边界。实时模式使用滚动窗口,X 轴范围随数据推进。
5.3 性能:replot() → update()
Qt Charts 使用场景图(QGraphicsScene)自动重绘,通常不需要手动调用 update()。但在数据更新后立即调用 m_chart->update() 可确保即时刷新。
5.4 图例位置
QCustomPlot 图例默认在底部,Qt Charts 默认在右侧。调整位置:
chart->legend()->setAnchorPosition(QChart::LegendAnchorPosition::LegendBottom);
5.5 条件编译保护
PLCMonitor 使用 #ifdef PLC_HAS_CHARTS 宏保护图表面板代码,确保在没有 Qt Charts 的构建环境中也能编译通过:
#ifdef PLC_HAS_CHARTS
#include <QChartView>
#include <QChart>
// …
#endif
⑥ 结语
GPL 合规是工业软件商业化的必经之路。QCustomPlot 虽然优秀,但 GPLv3 许可让它不适合闭源商业产品。
Qt Charts 作为替代方案,功能虽然略逊于 QCustomPlot,但完全满足工业监控软件的需求,且许可证友好。
迁移过程不算复杂,关键是充分评估、分步实施、全面测试。希望本文的经验能给你一些参考。
如果您也在工业监控领域有类似需求,欢迎交流探讨技术方案。
如果觉得这篇文章对你有帮助,欢迎点赞收藏,下期继续分享 PLCMonitor 的其他设计实践。
项目地址:https://github.com/freddiezhang1990/plcmonitor
欢迎下载 V2.0 安装包试用,并提出宝贵意见。
网硕互联帮助中心







评论前必须登录!
注册