云计算百科
云计算领域专业知识百科平台

GPL污染危机_QCustomPlot迁移实战

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 就必须:

  • 开源全部源码
  • 或以商业许可购买授权(QCustomPlot 不提供商业许可)
  • 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 类型Qt Charts 对应说明
    QCustomPlot QChartView + QChart Chart 是数据模型,View 是视图组件
    QCPGraph QLineSeries 线图系列
    QCPAxis QValueAxis / QDateTimeAxis X 轴建议用 DateTime
    QCPAxisRect 不再需要 QChartView 自动管理布局
    QCPGrid QValueAxis::setGridLineVisible() 网格线通过 Axis 控制
    legend QChart::legend() 图例由 Chart 管理

    关键方法映射

    QCustomPlot APIQt Charts 等效
    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)

    轴类型选择

    场景QCustomPlot 用法Qt Charts 推荐
    实时模式 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 安装包试用,并提出宝贵意见。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » GPL污染危机_QCustomPlot迁移实战
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!