【Qwt 7.0 系列】总体架构解析 ------ 从单体到三库模块化的演进
本文是 Qwt 7.0 系列介绍和教程,如果你正在寻找一个高性能、协议友好、同时支持 2D 和 3D 绘图的 Qt 数据可视化库,那么这篇文章就是为你准备的。
系列总述文章:Qwt 7.0 ------ 基于 Qt 的高性能 2D/3D 绘图库
概述 | 高性能曲线绘制 | 常用图表类型 | 高级科学图表 | 多坐标轴与布局 | 交互功能 | 3D 数据可视化 | 坐标轴与刻度 | 控件与辅助元素 | 总体架构解析 | matplotlib 风格绘图
一、引言
如果你用过原版 Qwt 6.x,一定对它的"一大坨"库印象深刻------所有 2D 绘图、控件、工具类全部打包成一个 .so / .dll,构建系统还是老派的 qmake。想要 3D 绘图?得额外引入一个独立的 QwtPlot3D 库,两套 API 风格、两套构建配置,维护起来相当痛苦。
Qwt 7.0 对底层架构进行了彻底重构,将原来的单体库拆分为 core + plot + plot3d 三个独立的共享库,全面迁移到 CMake 构建系统,并内置了原 QwtPlot3D 的 3D 绘图能力。这一架构改进不仅带来了更好的模块隔离和按需编译能力,也为后续的性能优化(如 SIMD 加速)和功能扩展(如 22 种科学 colormap 预设、3D 主题系统)打下了坚实基础。
本文将从架构演进的角度,深入解析 Qwt 7.0 的三库模块化设计。

二、Qwt 6.x 的架构回顾
要理解 Qwt 7.0 为什么这样重构,先得回顾原版的架构痛点。
2.1 单库结构
原版 Qwt 6.2.0 将所有代码编译为一个共享库。无论你只用到一条简单的曲线,还是用到完整的刻度引擎、栅格数据、SVG 导出,链接的都是同一个庞大的库文件。
这带来几个问题:
- 无法按需链接:即使用户只需要基础曲线绘制,也不得不把 SVG 导出、OpenGL 画布等无关代码全部链接进来
- 编译耦合:修改任何一个模块的代码,都要重新编译整个库
- 3D 绘图割裂 :原 QwtPlot3D 是一个完全独立的项目(来自 SciDAVis/qwtplot3d),有自己的一套构建系统和代码风格,与 Qwt 主库毫无代码复用
2.2 qmake 构建系统
原版使用 qmake 的 .pro 文件管理构建。qmake 在 Qt 生态中虽然经典,但存在明显局限:
- 不支持现代 CMake 的
find_package机制,第三方项目引入 Qwt 需要手动配置 include 路径和库路径 - 无法生成标准的 CMake Config 文件,与现代 C++ 项目集成困难
- 条件编译选项管理不够灵活
2.3 Qt 版本支持
原版 Qwt 6.2.0 主要面向 Qt 5,对 Qt 6 的支持有限。随着 Qt 6 的普及,许多 API 发生了变化(如 QMouseEvent::pos() → QMouseEvent::position()),需要大量条件编译代码来兼容。
三、Qwt 7.0 的三库架构
Qwt 7.0 的核心架构改进是将单体库拆分为三个独立的 shared library,每个库有清晰的职责边界。
3.1 三个共享库
| 目标 | CMake target | 输出名 | DLL 导出宏 |
|---|---|---|---|
| 公共基础 | qwt::core |
qwtcore.dll / libqwtcore.so |
QWTCORE_EXPORT |
| 2D 绘图 | qwt::plot |
qwtplot.dll / libqwtplot.so |
QWT_EXPORT |
| 3D 绘图 | qwt::plot3d |
qwtplot3d.dll / libqwtplot3d.so |
QWT3D_EXPORT |
3.2 依赖关系
三个库的依赖关系非常清晰:
┌──────────────────────────┐
│ qwt::core │ ← 基础工具库(颜色、数学、数据类型、几何、变换、时间等)
└──────────────────────────┘
↗ ↖
┌──────────────┐ ┌───────────────┐
│ qwt::plot │ │ qwt::plot3d │
│ (2D) │ │ (3D) │
└──────────────┘ └───────────────┘
核心原则:plot 和 plot3d 都依赖 core,但彼此互不依赖。
这意味着什么?如果你只做 2D 绘图,链接 qwt::plot 就够了,完全不会引入任何 OpenGL / 3D 相关的代码和依赖。反之亦然。这种解耦设计让依赖关系最小化,编译和部署都更加轻量。
3.3 各模块的 Qt 依赖
| 模块 | Qt 依赖(public) | Qt 依赖(private) | 可选依赖 |
|---|---|---|---|
| core | Core, Gui |
--- | --- |
| plot | Core, Gui, Widgets |
Concurrent, PrintSupport |
OpenGL, OpenGLWidgets(Qt6), Svg |
| plot3d | Core, Gui, Widgets, OpenGL, OpenGLWidgets(Qt6) |
--- | 外部 gl2ps(找不到则内置) |
注意 plot3d 还依赖 OpenGL::GLU,这是 3D 渲染的必需品。
关键设计:这些 Qt 依赖在 find_package(qwt) 引入时会自动传递 。你只需要 target_link_libraries(your_app PRIVATE qwt::plot),CMake 会自动把 Qt 的 Core、Gui、Widgets 等依赖全部加上,无需手动指定。
四、Core 模块详解
qwt::core 是整个项目的基础工具库,被 plot 和 plot3d 共享。它包含 28 个模块文件,涵盖了从颜色管理到坐标变换的全部基础能力。

4.1 模块分类
| 类别 | 核心文件 | 说明 |
|---|---|---|
| 全局 | qwtcore_global.h |
模块导出宏 QWTCORE_EXPORT |
| 颜色工具 | qwt_colormap.h, qwt_color_cycle.h, qwt_colormap_preset.h |
QwtColorMap 及子类、颜色循环、22 种科学 colormap 预设 |
| 数学工具 | qwt_math.h, qwt_simd_argminmax.h |
数学常量与工具函数、SSE2/AVX2/NEON 加速的 argmin/argmax |
| 数据类型 | qwt_interval.h, qwt_point_3d.h, qwt_point_polar.h, qwt_samples.h, qwt_box_statistics.h |
区间、三维点、极坐标点、样本数据结构、箱线图统计量 |
| 几何算法 | qwt_bezier.h, qwt_clipper.h |
贝塞尔曲线(de Casteljau 算法)、多边形裁剪 |
| 坐标变换 | qwt_transform.h, qwt_scale_map.h, qwt_scale_div.h, qwt_scale_engine.h |
变换函数(线性/对数等)、坐标映射、刻度划分、刻度引擎 |
| 时间处理 | qwt_date.h, qwt_system_clock.h |
日期时间工具、高精度计时器 |
| 通用算法 | qwt_algorithm.hpp, qwt_qt5qt6_compat.hpp |
通用算法模板、Qt5/Qt6 兼容层 |
| 数据容器 | qwt_grid_data.hpp |
网格数据容器 |
| 数据系列 | qwt_series_data.h, qwt_point_data.h, qwt_series_store.h |
数据系列基类模板、点数据实现、数据存储模板 |
| 栅格数据 | qwt_raster_data.h, qwt_matrix_raster_data.h, qwt_grid_raster_data.h |
栅格数据基类、矩阵栅格数据、网格栅格数据(v7 新增) |
4.2 为什么要独立出 core
将基础能力独立为 core 库的好处是显而易见的:
- 代码复用:plot 和 plot3d 共享同一套颜色映射、坐标变换、数据类型,避免重复代码
- 按需编译:如果有人只需要 Qwt 的数学工具或颜色映射(比如用于自定义绘图),可以只链接 core,不需要引入整个 2D/3D 绘图栈
- 关注点分离:core 不依赖任何 Widgets,可以用于无 GUI 的后端计算场景
举个例子,core 中的 QwtColorMapPreset 提供了 22 种科学 colormap 预设(viridis、plasma、jet、hot 等),这些预设既可用于 2D 的 QwtPlotSpectrogram,也可通过 ColorMapColor 适配器用于 3D 表面图。一个预设定义,两处复用。
4.3 SIMD 加速
core 模块中的 qwt_simd_argminmax.h 是一个值得关注的性能优化点。它利用 SSE2 / AVX2(x86)和 NEON(ARM)指令集来加速大规模数据的 argmin/argmax 运算。在处理百万级数据点的曲线时,这一优化可以显著减少渲染延迟。
五、Plot 模块详解
qwt::plot 是 2D 绘图的核心模块,承载了原版 Qwt 的全部 2D 绘图能力。
5.1 核心功能
plot 模块包含但不限于:
QwtPlot:主绘图容器,支持多轴、寄生绘图(Parasite Plot)- 曲线系列:
QwtPlotCurve、QwtPlotSpectrogram、QwtPlotTradingCurve等 - 交互工具:缩放器、平移器、拾取器(Picker)
- 刻度绘制:
QwtScaleDraw、QwtDateScaleDraw - 图形元素:
QwtGraphic、QwtColumnSymbol、QwtIntervalSymbol - 布局管理:
QwtDynGridLayout - 放大镜:
QwtMagnifier
5.2 链接方式
plot 模块通过 PUBLIC 链接依赖 core:
cmake
target_link_libraries(${QWTPLOT_LIB_NAME} PUBLIC
${QWT_LIB_NAME}::core
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets
)
注意这里用的是 PUBLIC 而非 PRIVATE。这意味着当你的项目链接 qwt::plot 时,core 的头文件路径和符号会自动传递过来。你不需要单独再链接 qwt::core。
5.3 可选功能
plot 模块有两个重要的条件编译选项:
QWT_CONFIG_QWTOPENGL(默认 ON):启用 OpenGL 画布支持。启用后会额外依赖Qt::OpenGL(Qt6 还需Qt::OpenGLWidgets),允许使用 GPU 加速渲染高频数据QWT_CONFIG_QWTSVG(默认 ON):启用 SVG 图像显示和导出,额外依赖Qt::Svg
如果你不需要这些功能,可以在 CMake 配置时关闭以减少依赖:
cmake
cmake -S . -B build -DQWT_CONFIG_QWTOPENGL=OFF -DQWT_CONFIG_QWTSVG=OFF
六、Plot3D 模块详解
qwt::plot3d 是 3D 绘图模块,在 Qwt 7.1 之后正式整合进了主项目。

6.1 整合自 QwtPlot3D
原版 Qwt 6.x 时代,3D 绘图需要单独引入 QwtPlot3D 库,这个库来自 SciDAVis/qwtplot3d,有完全独立的代码库和构建系统。
Qwt 7.0 将 QwtPlot3D 的代码整合进了主项目,统一了构建系统和代码风格,通过 QWT_CONFIG_QWTPLOT_3D 选项控制是否编译。这解决了几个长期痛点:
- 不再需要维护两套项目
- 3D 绘图可以直接复用 core 模块的颜色映射、数据类型
- 用户只需一次
find_package(qwt)就能同时获得 2D 和 3D 能力
6.2 gl2ps 回退机制
plot3d 模块支持将 3D 场景导出为矢量图(PostScript / PDF),这依赖 gl2ps 库。CMakeLists.txt 中实现了一个优雅的回退机制:
cmake
find_library(GL2PS_LIBRARY NAMES gl2ps)
if(GL2PS_LIBRARY)
# 系统已安装 gl2ps,直接链接
find_path(GL2PS_INCLUDE_DIR NAMES gl2ps.h REQUIRED)
target_link_libraries(${QWTPLOT3D_LIB_NAME} ${GL2PS_LIBRARY})
else()
# 系统没有 gl2ps,编译内置的 3rdparty/gl2ps 源码
target_sources(${QWTPLOT3D_LIB_NAME} PRIVATE
"3rdparty/gl2ps/gl2ps.c" "3rdparty/gl2ps/gl2ps.h")
target_include_directories(${QWTPLOT3D_LIB_NAME} PRIVATE "3rdparty/gl2ps")
endif()
这段逻辑确保了:无论目标系统是否预装了 gl2ps,3D 矢量导出功能都能正常工作。系统有就用系统的,没有就编译内置的。这是"开箱即用"理念的典型实践。
6.3 Qwt3D 主题系统
plot3d 模块引入了 Qwt3D::Qwt3DTheme 主题系统,封装了 3D 绘图的全部视觉属性:
- 背景色、网格色/线宽
- 数据 colormap
- 坐标轴颜色、标题样式
- 光照预设:
NoLighting、FlatLight、Studio、Outdoor、Soft - 着色模式、材质参数
内置 10 种预设主题:Default、Dark、Scientific、Warm、Cool、Matplotlib、EarthTones、Ocean、HighContrast、Presentation。
cpp
// 使用预设主题
plot->applyTheme(Qwt3D::Qwt3DTheme::Dark);
// 手动定制
Qwt3D::Qwt3DTheme theme(Qwt3D::Qwt3DTheme::Scientific);
theme.setDataColorPreset("plasma"); // 使用 core 模块的 colormap 预设
theme.setShininess(20.0);
theme.apply(plot); // plot 是 Qwt3D::Plot3D* 指针
ColorMapColor 适配器桥接了 core 模块的 QwtColorMap 到 3D 的 Qwt3D::Color 接口,使得 22 种科学 colormap 预设可以直接用于 3D 表面图。这正是三库架构带来的复用优势------core 定义 colormap,plot 和 plot3d 各自消费。
七、CMake 构建系统
Qwt 7.0 全面迁移到 CMake,放弃了 qmake。这是现代化的关键一步。
7.1 find_package 一键引入
安装 Qwt 后,在你的项目中只需:
cmake
# 指定 Qwt 的安装目录(如果非默认路径)
set(qwt_DIR "C:/path/to/install/lib/cmake/qwt")
# 一行加载
find_package(qwt REQUIRED)
# 按需链接
target_link_libraries(your_app PRIVATE qwt::plot) # 只用 2D
# 或者
target_link_libraries(your_app PRIVATE qwt::plot3d) # 只用 3D
# 或者
target_link_libraries(your_app PRIVATE qwt::plot qwt::plot3d) # 2D + 3D
链接 qwt::plot 会自动传递 qwt::core 及其 Qt 依赖(Core、Gui、Widgets);链接 qwt::plot3d 会自动传递 qwt::core、Qt OpenGL 系列依赖和 OpenGL::GLU。你不需要手动管理任何传递依赖。
7.2 三个独立的 CMake target
每个库都创建了命名空间别名,使用现代 CMake 的 ALIAS 机制:
cmake
# core 模块
add_library(${QWT_LIB_NAME}::${QWTCORE_LIB_NAME} ALIAS ${QWTCORE_LIB_NAME})
# plot 模块
add_library(${QWT_LIB_NAME}::${QWTPLOT_LIB_NAME} ALIAS ${QWTPLOT_LIB_NAME})
# plot3d 模块
add_library(${QWT_LIB_NAME}::${QWTPLOT3D_LIB_NAME} ALIAS ${QWTPLOT3D_LIB_NAME})
用户始终通过 qwt::core、qwt::plot、qwt::plot3d 这三个带命名空间的 target 来引用,符合现代 CMake 的最佳实践。
7.3 条件编译选项
| CMake 选项 | 默认 | 控制内容 |
|---|---|---|
QWT_CONFIG_QWTPLOT |
ON | 核心绘图:QwtPlot、曲线、网格、缩放等 |
QWT_CONFIG_QWTPOLAR |
ON | 极坐标绘图子模块 |
QWT_CONFIG_QWTWIDGETS |
ON | 控件:滑块、旋钮、刻度盘、温度计等 |
QWT_CONFIG_QWTSVG |
ON | SVG 导出/渲染 |
QWT_CONFIG_QWTOPENGL |
ON | OpenGL 画布 |
QWT_CONFIG_QWTPLOT_3D |
ON | 3D 绘图模块 |
QWT_CONFIG_BUILD_EXAMPLE |
ON | 构建示例程序 |
QWT_CONFIG_BUILD_PLAYGROUND |
ON | 构建实验性代码 |
QWT_CONFIG_BUILD_STATIC_EXAMPLE |
ON | 构建静态链接示例 |
QWT_CONFIG_BUILD_TESTS |
OFF | 测试构建 |
例如,只构建核心库,不编译示例和实验代码:
cmake
cmake -S . -B build -G "Visual Studio 16 2019" -A x64 ^
-DCMAKE_PREFIX_PATH="D:/Qt/6.7.3/msvc2019_64" ^
-DQWT_CONFIG_BUILD_EXAMPLE=OFF ^
-DQWT_CONFIG_BUILD_PLAYGROUND=OFF
7.4 与 qmake 的对比
| 对比项 | qmake (Qwt 6.x) | CMake (Qwt 7.0) |
|---|---|---|
| 包发现 | 手动配置 include/lib 路径 | find_package(qwt) 标准 CMake 机制 |
| 依赖传递 | 无自动传递 | PUBLIC/PRIVATE 依赖自动传递 |
| 条件编译 | .pro 文件中 define |
标准 CMake option |
| 跨平台 | 需多套 .pro | 单一 CMakeLists.txt |
| IDE 支持 | Qt Creator 为主 | CLion / VS / Qt Creator / VSCode 全支持 |
八、PIMPL 模式
Qwt 7.0 在 PIMPL(Pointer to Implementation)模式上有一个值得注意的设计:它没有使用 Qt 标准的 Q_DECLARE_PRIVATE 宏,而是自定义了一套宏。
8.1 自定义宏体系
这套宏定义在 qwt_global.h 中:
cpp
// 头文件:声明 PIMPL
class QWT_EXPORT QwtFoo : public QWidget {
QWT_DECLARE_PRIVATE(QwtFoo) // → class PrivateData; unique_ptr<PrivateData> m_data;
};
// 源文件:实现 PrivateData
class QwtFoo::PrivateData {
QWT_DECLARE_PUBLIC(QwtFoo) // → QwtFoo* q_ptr;
};
// 构造函数初始化
QwtFoo::QwtFoo() : QWT_PIMPL_CONSTRUCT {} // → m_data(qwt_make_unique<PrivateData>(this))
// 访问私有时使用
QWT_D(d); // PrivateData* d = d_func()
QWT_DC(d); // const PrivateData* d = d_func()
8.2 与 Qt 标准 PIMPL 的区别
Qt 标准的 Q_DECLARE_PRIVATE 使用的是间接指针(d_ptr 指向 QObjectPrivate 子类),而 Qwt 的自定义方案有一个关键区别:
m_data直接存储PrivateData(通过unique_ptr),不使用堆指针重定向- 非_const_ 方法通过
QWT_D()访问私有数据 - const 方法通过
QWT_DC()访问私有数据
这种设计简化了私有数据的访问路径,同时在 const 正确性上更加严格------QWT_DC 确保 const 方法只能 const 访问私有数据。
九、Qt5/Qt6 兼容层
Qwt 7.0 同时支持 Qt 5.12+ 和 Qt 6.x。为了优雅地处理两个版本之间的 API 差异,core 模块提供了 qwt::compat 命名空间下的兼容层(定义在 qwt_qt5qt6_compat.hpp 中)。
9.1 核心兼容函数
cpp
// 鼠标/触摸事件位置
// Qt5: event->pos() → QPoint
// Qt6: event->position().toPoint() → QPoint
qwt::compat::eventPos(event);
// 滚轮事件增量
// Qt5: event->delta() → int
// Qt6: event->angleDelta().y() → int
qwt::compat::wheelEventDelta(event);
// 字体度量(文本宽度)
// Qt5.12-: fm.width(str) → int
// Qt5.12+: fm.horizontalAdvance(str) → int
qwt::compat::horizontalAdvance(fm, str);
9.2 设计理念
这套兼容层的理念是:一处封装,处处使用 。所有与 Qt 版本相关的 API 差异都集中在 qwt::compat 命名空间中处理,业务代码只调用兼容函数,不需要写任何 #if QT_VERSION 条件编译。
对比一下没有兼容层时的写法:
cpp
// 没有兼容层 ------ 到处都是条件编译
QPoint pos;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
pos = event->position().toPoint();
#else
pos = event->pos();
#endif
// 有兼容层 ------ 一行搞定
QPoint pos = qwt::compat::eventPos(event);
这使得代码可读性大幅提升,也降低了维护成本。当未来需要支持新的 Qt 版本时,只需修改兼容层即可。
十、与 Qwt 6.x 架构对比总结
| 对比维度 | Qwt 6.x | Qwt 7.0 |
|---|---|---|
| 库结构 | 单体库(一个 .so/.dll) | 三库:core + plot + plot3d |
| 构建系统 | qmake (.pro) | CMake (CMakeLists.txt) |
| 3D 绘图 | 需独立引入 QwtPlot3D 库 | 内置 plot3d 模块,统一管理 |
| 包引入 | 手动配置路径 | find_package(qwt) 标准机制 |
| Qt 版本 | 主要 Qt5 | Qt 5.12+ / Qt 6.x 双支持 |
| 兼容方案 | #if QT_VERSION 散落各处 |
qwt::compat 命名空间统一封装 |
| PIMPL 模式 | 标准 Qt Q_DECLARE_PRIVATE |
自定义宏 QWT_DECLARE_PRIVATE / QWT_D / QWT_DC |
| 颜色映射 | 基础 colormap 类 | core 模块 + 22 种科学预设 + 3D 适配器 |
| 性能优化 | 无 | SIMD 加速 argmin/argmax(SSE2/AVX2/NEON) |
| 矢量导出(3D) | 依赖外部 gl2ps | 内置 gl2ps 回退,开箱即用 |
| 条件编译 | .pro 中的 define | 标准 CMake option,粒度更细 |
十一、总结
Qwt 7.0 的三库模块化架构是一次深思熟虑的重构。它不是简单地把代码拆成三份,而是重新梳理了模块间的依赖关系:
- core 作为基础工具库,提供颜色、数学、数据类型、坐标变换等通用能力,被 plot 和 plot3d 共享
- plot 专注于 2D 绘图,按需引入 OpenGL 和 SVG
- plot3d 整合了原 QwtPlot3D,内置 gl2ps 回退,通过
ColorMapColor适配器复用 core 的 colormap 预设
配合 CMake 构建系统、qwt::compat 兼容层和自定义 PIMPL 宏,Qwt 7.0 在保持 API 兼容性的同时,实现了现代化的工程架构。无论你是只需要简单的 2D 曲线,还是需要完整的 2D + 3D 科学可视化方案,都可以通过 find_package(qwt) 按需引入,不多不少。
这一架构为后续的性能优化和功能扩展打下了坚实基础。在后续系列文章中,我们将深入各个模块的具体实现细节。
系列文章
- 第 1 篇:快速入门与核心新特性概览
- 第 2 篇:曲线绘图详解 ------ 从基础到百万级数据性能优化
- 第 3 篇:常用图表类型实战 ------ 柱状图、散点图、箱线图与直方图
- 第 4 篇:高级科学图表 ------ 光谱图、向量场、K线图与极坐标绘图
- 第 5 篇:多坐标轴与多绘图布局 ------ 寄生绘图与 QwtFigure 容器
- 第 6 篇:交互功能详解 ------ 平移、缩放、坐标轴交互与数据拾取
- 第 7 篇:3D 数据可视化 ------ OpenGL 高性能三维绘图
- 第 8 篇:坐标轴与刻度系统 ------ 刻度引擎、网格、图例与刻度朝内
- 第 9 篇:控件与辅助元素 ------ 滑块旋钮、标记与装饰
- 第 10 篇:总体架构解析 ------ 从单体到三库模块化的演进
- 第 11 篇:matplotlib 风格绘图 ------ QwtPyPlot 接口详解
相关链接