【Qwt 7.0 系列】多坐标轴与多绘图布局 —— 寄生绘图与 QwtFigure 容器
【Qwt 7.0 系列】多坐标轴与多绘图布局 —— 寄生绘图与 QwtFigure 容器
本文是 Qwt 7.0 系列介绍和教程,如果你正在寻找一个高性能、协议友好、同时支持 2D 和 3D 绘图的 Qt 数据可视化库,那么这篇文章就是为你准备的。
系列总述文章:Qwt 7.0 —— 基于 Qt 的高性能 2D/3D 绘图库
概述 | 高性能曲线绘制 | 常用图表类型 | 高级科学图表 | 多坐标轴与布局 | 交互功能 | 3D 数据可视化 | 坐标轴与刻度 | 控件与辅助元素 | 总体架构解析 | matplotlib 风格绘图
项目地址:GitHub | Gitee | 在线文档
引言
如果你用过原版 Qwt 6.x,一定遇到过这样的痛点:一个QwtPlot只能拥有上下左右四个坐标轴。当你需要在同一张图上绘制温度(℃)、压力(kPa)、流量(L/s)三种不同量级的数据时,两个 Y 轴根本不够用。
在 matplotlib 中,这个问题通过twinx()/twiny()轻松解决;但在 Qt 的绘图世界里,长期以来没有优雅的方案。Qwt 7.0 带来了全新的寄生绘图(Parasite Axes)机制和QwtFigure 容器,彻底打破了这一限制。这两个特性是 Qwt 7.0 最重要的架构创新之一,原版 Qwt 6.x 完全不支持。
本文将带你深入理解这两个全新功能,从原理到实战,一步步掌握多坐标轴叠加和多绘图布局的技巧。
一、寄生绘图(Parasite Axes)—— 多坐标轴系统
1.1 工作原理
寄生绘图的核心思想很简单:在宿主绘图的画布上方,叠加一个透明的子绘图。这个子绘图拥有独立的坐标系统,但和宿主绘图共享同一块绘图区域。
它有三个关键特征:
| 特征 | 说明 |
|---|---|
| 画布区域一致 | 寄生绘图的画布与宿主绘图保持相同尺寸 |
| 独立坐标系统 | 可以拥有自己的刻度范围、标签、单位 |
| 透明背景 | 只显示坐标轴和曲线,不遮挡宿主绘图 |
你可以把寄生绘图想象成一层"透明薄膜",贴在宿主绘图上面,每层薄膜各自携带一套独立的坐标轴。
1.2 创建寄生绘图:createParasitePlot()
通过QwtPlot::createParasitePlot()方法创建寄生绘图,签名如下:
QwtPlot*createParasitePlot(QwtAxis::Position enableAxis);参数enableAxis指定寄生绘图初始显示的坐标轴位置,其他坐标轴默认隐藏。下面是一个完整的示例:
// 创建宿主绘图QwtPlot*hostPlot=newQwtPlot();// ... 设置宿主绘图的参数(曲线、标题等)////////////////////////////////////////////////////////// 添加寄生坐标系////////////////////////////////////////////////////////QwtPlot*parasitePlot=hostPlot->createParasitePlot(QwtAxis::YLeft);// 额外启用寄生绘图的其他坐标轴parasitePlot->enableAxis(QwtAxis::YRight,true);parasitePlot->enableAxis(QwtAxis::XTop,true);// 设置寄生绘图与宿主共享 X 轴parasitePlot->setParasiteShareAxis(QwtAxis::XBottom);// 设置寄生轴标题parasitePlot->setAxisTitle(QwtAxis::YLeft,"Y2 Left Axis");parasitePlot->setAxisTitle(QwtAxis::YRight,"Y2 Right Axis");parasitePlot->setAxisTitle(QwtAxis::XTop,"X2 Top Axis");// 在寄生绘图上添加曲线QColorcurColor(255,127,14);// 橙色,与宿主曲线区分QwtPlotCurve*parasiteCurve=newQwtPlotCurve("parasite sine Wave 1");parasiteCurve->setSamples(generateSampleData(100,2000,2.3));parasiteCurve->attach(parasitePlot);parasiteCurve->setPen(curColor,1.5);parasiteCurve->setRenderHint(QwtPlotItem::RenderAntialiased,true);// 给寄生轴的刻度上色,方便区分parasitePlot->axisWidget(QwtAxis::YLeft)->setScaleColor(curColor);parasitePlot->axisWidget(QwtAxis::YRight)->setScaleColor(curColor);parasitePlot->axisWidget(QwtAxis::XTop)->setScaleColor(curColor);运行效果如下(单寄生绘图):
1.3 多寄生绘图叠加:任意数量的坐标轴
Qwt 7.0 的强大之处在于——寄生绘图的数量没有限制。只需在宿主绘图上多次调用createParasitePlot(),即可创建任意多的坐标轴层。下面是创建第二个寄生绘图的代码:
////////////////////////////////////////////////////////// 添加第二个寄生坐标系////////////////////////////////////////////////////////QwtPlot*parasitePlot2=hostPlot->createParasitePlot(QwtAxis::YLeft);// 启用额外坐标轴并设置共享parasitePlot2->enableAxis(QwtAxis::YRight,true);parasitePlot2->enableAxis(QwtAxis::XBottom,true);parasitePlot2->setParasiteShareAxis(QwtAxis::XTop);// 设置轴标题parasitePlot2->setAxisTitle(QwtAxis::YLeft,"Y3 Left Axis");parasitePlot2->setAxisTitle(QwtAxis::YRight,"Y3 Right Axis");parasitePlot2->setAxisTitle(QwtAxis::XBottom,"X3 Bottom Axis");// 在第二个寄生绘图上添加曲线QColorcurColor2(192,43,149);// 紫色QwtPlotCurve*parasiteCurve2=newQwtPlotCurve("parasite sine Wave 2");parasiteCurve2->setSamples(generateSampleData(200,1000,4.3));parasiteCurve2->attach(parasitePlot2);parasiteCurve2->setPen(curColor2,1);parasiteCurve2->setRenderHint(QwtPlotItem::RenderAntialiased,true);// 刻度上色parasitePlot2->axisWidget(QwtAxis::YLeft)->setScaleColor(curColor2);parasitePlot2->axisWidget(QwtAxis::YRight)->setScaleColor(curColor2);parasitePlot2->axisWidget(QwtAxis::XBottom)->setScaleColor(curColor2);两个寄生绘图叠加后的效果:
可以看到,图上有三套不同颜色的坐标轴,分别对应宿主绘图和两个寄生绘图。这在原版 Qwt 6.x 中是根本做不到的。
1.4 寄生绘图的层级关系
寄生绘图有明确的层级关系,决定了坐标轴的布局顺序:
- 先添加的寄生绘图处于低层级,其坐标轴靠近宿主绘图
- 后添加的寄生绘图处于高层级,其坐标轴远离宿主绘图
布局时,宿主绘图先完成自身布局,然后从低层级到高层级依次布局寄生绘图的坐标轴。
1.5 坐标轴共享配置
通过setParasiteShareAxis()方法,可以让寄生绘图与宿主绘图共享某个坐标轴。共享后,该轴的刻度范围会自动同步:
// 寄生绘图的 X 轴与宿主共享parasitePlot->setParasiteShareAxis(QwtAxis::XBottom);这在 X 轴代表时间等公共维度时非常有用——多个 Y 轴各自独立,但共享同一条 X 轴。
1.6 生命周期管理与遍历
生命周期:寄生绘图的生命周期与宿主绘图绑定。宿主绘图销毁时,所有寄生绘图自动销毁,无需手动管理。
判断与遍历:通过以下方法可以区分和获取绘图对象:
// 判断是否为宿主/寄生绘图boolisHost=plot->isHostPlot();boolisParasite=plot->isParasitePlot();// 获取宿主绘图(在寄生绘图上调用)QwtPlot*host=parasitePlot->hostPlot();// 获取所有寄生绘图(在宿主绘图上调用)QList<QwtPlot*>parasites=hostPlot->parasitePlots();// 获取所有绘图列表(宿主 + 寄生,在任何绘图上调用均可)constQList<QwtPlot*>plotList=plot->plotList();// plotList[0] 是宿主绘图本身for(QwtPlot*p:plotList){constQwtPlotItemList&items=p->itemList();for(QwtPlotItem*item:items){// 遍历所有绘图项}}注意:一个绘图不会既是寄生绘图又是宿主绘图。在寄生绘图上调用
createParasitePlot()将返回nullptr。
二、QwtFigure —— 多绘图布局容器
2.1 类似 matplotlib Figure 的概念
如果说寄生绘图解决的是"一个画布上多套坐标轴"的问题,那么QwtFigure解决的就是"一个窗口里多个子图"的问题。
QwtFigure是一个类似 matplotlibFigure的容器窗口,用于组织和管理多个QwtPlot组件。它提供了两种布局方式:归一化坐标布局和网格布局。
2.2 归一化坐标布局
归一化坐标就是以[0, 1]范围的百分比来定位子图,遵循 Qt 标准的左上角坐标系:
QwtFigure*figure=newQwtFigure();figure->setSizeInches(8,6);// 设置图形尺寸为 8x6 英寸figure->setFaceColor(Qt::white);// 设置背景色QwtPlot*plot1=newQwtPlot();// 添加到左上角四分之一区域figure->addAxes(plot1,QRectF(0.0,0.0,0.5,0.5));// 或者使用分离参数形式QwtPlot*plot2=newQwtPlot();figure->addAxes(plot2,0.5,0.0,0.5,0.5);// 右上角四个参数依次是:x, y, width, height,均为占整个 Figure 窗口的百分比。
2.3 网格布局(类似 subplot)
如果你熟悉 matplotlib 的subplot,那么 QwtFigure 的网格布局会让你感到亲切:
// 3x2 网格,第1行第0列(0-based)QwtPlot*plot3=newQwtPlot();figure->addGridAxes(plot3,3,2,1,0);// 3x2 网格,第1行第1列QwtPlot*plot4=newQwtPlot();figure->addGridAxes(plot4,3,2,1,1);// 3x2 网格,第2行,跨2列QwtPlot*hostPlot=newQwtPlot();figure->addGridAxes(hostPlot,3,2,2,0,1,2);addGridAxes的参数含义:(plot, totalRows, totalCols, row, col, rowSpan, colSpan),其中rowSpan和colSpan可选,用于跨行跨列。
下面是一个综合使用归一化坐标和网格布局的完整示例效果:
2.4 坐标轴对齐功能
当多个子图的 Y 轴刻度范围差异较大时,画布的水平边界可能不对齐,视觉上不够整齐:
QwtFigure 提供了addAxisAlignment()方法来解决这个问题:
// plot1、plot3、hostPlot 的左坐标轴对齐figure->addAxisAlignment({plot1,plot3,hostPlot},QwtAxis::YLeft);// plot2、plot4 的左坐标轴对齐figure->addAxisAlignment({plot2,plot4},QwtAxis::YLeft);对齐后,相关子图的画布边界会自动调整到同一水平线:
注意:只有可见的坐标轴才能对齐。如果某个子图的对应坐标轴未显示,则无法参与对齐。
2.5 交互式操作蒙版:QwtFigureWidgetOverlay
QwtFigure 还提供了一个交互式操作蒙版QwtFigureWidgetOverlay,允许用户在运行时通过鼠标拖拽来调整子绘图的位置和大小,类似于可视化设计器中的操作体验。
QwtFigure*figure=newQwtFigure();// ... 添加子绘图 ...// 创建并显示操作蒙版QwtFigureWidgetOverlay*overlay=newQwtFigureWidgetOverlay(figure);overlay->show();运行效果如下:
蒙版提供两项核心功能,可通过枚举控制:
enumBuiltInFunctionsFlag{FunSelectCurrentPlot=1,// 选择当前绘图FunResizePlot=2// 调整绘图大小};启用/禁用特定功能:
// 只保留选择功能,禁用尺寸调整overlay->setBuiltInFunctionsEnable(QwtFigureWidgetOverlay::FunResizePlot,false);自定义蒙版外观:
overlay->setBorderPen(QPen(Qt::red,2,Qt::DashLine));overlay->setControlPointBrush(QBrush(Qt::green));overlay->setControlPointSize(QSize(10,10));overlay->showPercentText(false);// 隐藏百分比文本蒙版还提供两个有用的信号:widgetNormGeometryChanged()(子图几何尺寸变化)和activeWidgetChanged()(当前激活子图变化),方便你响应用户操作。
三、绘图布局调整
无论是单绘图还是多绘图场景,QwtPlotLayout都是控制绘图内部空间分配的核心引擎。它负责组织标题、图例、坐标轴和画布区域的位置与大小。
3.1 画布边距
画布边距控制画布与坐标轴之间的空白:
QwtPlotLayout*layout=plot->plotLayout();// 设置所有坐标轴的画布边距为 10 像素layout->setCanvasMargin(10);// 仅设置左侧 Y 轴的画布边距layout->setCanvasMargin(15,QwtAxis::YLeft);3.2 组件间距与画布对齐
// 组件(标题、画布、图例、页脚)之间的间距layout->setSpacing(8);// 画布与坐标轴刻度对齐(默认开启)layout->setAlignCanvasToScales(true);// 也可以按轴单独控制layout->setAlignCanvasToScale(QwtAxis::XBottom,true);layout->setAlignCanvasToScale(QwtAxis::YLeft,false);对齐模式下,画布边界与刻度线精确对齐;不对齐模式下,画布保持固定大小,刻度标签完整显示。
3.3 图例位置
// 图例放在右侧,占 20% 宽度layout->setLegendPosition(QwtPlot::RightLegend,0.2);// 图例放在底部layout->setLegendPosition(QwtPlot::BottomLegend);| 位置枚举 | 说明 |
|---|---|
QwtPlot::LeftLegend | 图例在 YLeft 轴左侧 |
QwtPlot::RightLegend | 图例在 YRight 轴右侧 |
QwtPlot::BottomLegend | 图例在页脚下方 |
QwtPlot::TopLegend | 图例在标题上方 |
3.4 寄生绘图的轴边距
对于寄生绘图,坐标轴之间的间距通过QwtScaleWidget的两个属性控制:
- margin:坐标轴靠近画布一侧到画布的距离,默认 0(紧贴画布)
- edgeMargin:坐标轴靠近绘图边界一侧到边界的距离,默认 0(紧贴边框)
添加寄生轴后,在显示前应手动调用updateAllAxisEdgeMargin()自动计算各层轴的间距,避免重叠:
hostPlot->updateAllAxisEdgeMargin();该函数会遍历宿主及所有寄生轴,逐层计算 margin 和 edgeMargin,使各层坐标轴均匀分布。
四、与原版 Qwt 6.x 的区别
这是很多读者关心的问题,下面做一个对比总结:
| 特性 | 原版 Qwt 6.x | Qwt 7.0 |
|---|---|---|
| 坐标轴数量 | 固定 4 个(上下左右) | 任意多个(寄生绘图叠加) |
| 多绘图布局容器 | 无(需手动用 QLayout 管理) | QwtFigure 容器,支持归一化坐标和网格布局 |
| 坐标轴对齐 | 无 | addAxisAlignment() 自动对齐 |
| 交互式布局调整 | 无 | QwtFigureWidgetOverlay 蒙版 |
| 多 Y 轴场景 | 需要复杂的 hack | 原生支持,API 简洁 |
寄生绘图和 QwtFigure 都是 Qwt 7.0 全新引入的功能,原版 Qwt 6.x 完全不支持。这是 7.0 最重要的架构创新之一,借鉴了 matplotlib 的设计理念,但完全基于 Qt 原生组件实现,性能和集成度更好。
五、总结
本文介绍了 Qwt 7.0 的两大布局利器:
寄生绘图:通过
createParasitePlot()在同一画布上叠加任意多套独立坐标轴,完美解决多 Y 轴、多量级数据的可视化需求。寄生绘图与宿主绘图共享画布、独立坐标系、生命周期自动管理。QwtFigure 容器:类似 matplotlib Figure 的多绘图布局容器,支持归一化坐标定位和网格布局,还能通过
addAxisAlignment()实现子图坐标轴对齐,配合QwtFigureWidgetOverlay蒙版实现交互式布局调整。
这两个功能让 Qwt 7.0 在多轴、多图场景下的能力大幅跃升,从"够用"走向"好用"。
系列文章
系列总述:Qwt 7.0 —— 基于 Qt 的高性能 2D/3D 绘图库
- 第 1 篇:快速入门与核心新特性概览
- 第 2 篇:曲线绘图详解 —— 从基础到百万级数据性能优化
- 第 3 篇:常用图表类型实战 —— 柱状图、散点图、箱线图与直方图
- 第 4 篇:高级科学图表 —— 光谱图、向量场、K线图与极坐标绘图
- 第 5 篇:多坐标轴与多绘图布局 —— 寄生绘图与 QwtFigure 容器
- 第 6 篇:交互功能详解 —— 平移、缩放、坐标轴交互与数据拾取
- 第 7 篇:3D 数据可视化 —— OpenGL 高性能三维绘图
- 第 8 篇:坐标轴与刻度系统 —— 刻度引擎、网格、图例与刻度朝内
- 第 9 篇:控件与辅助元素 —— 滑块旋钮、标记与装饰
- 第 10 篇:总体架构解析 —— 从单体到三库模块化的演进
- 第 11 篇:matplotlib 风格绘图 —— QwtPyPlot 接口详解
相关链接
- 项目地址:https://github.com/czyt1988/QWT
- Gitee 镜像:https://gitee.com/czyt1988/QWT
- 在线文档:https://czyt1988.github.io/QWT/zh/
- 系列总述:https://blog.csdn.net/czyt1988/article/details/160193393