
【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*hostPlotnewQwtPlot();// ... 设置宿主绘图的参数曲线、标题等////////////////////////////////////////////////////////// 添加寄生坐标系////////////////////////////////////////////////////////QwtPlot*parasitePlothostPlot-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*parasiteCurvenewQwtPlotCurve(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*parasitePlot2hostPlot-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*parasiteCurve2newQwtPlotCurve(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 生命周期管理与遍历生命周期寄生绘图的生命周期与宿主绘图绑定。宿主绘图销毁时所有寄生绘图自动销毁无需手动管理。判断与遍历通过以下方法可以区分和获取绘图对象// 判断是否为宿主/寄生绘图boolisHostplot-isHostPlot();boolisParasiteplot-isParasitePlot();// 获取宿主绘图在寄生绘图上调用QwtPlot*hostparasitePlot-hostPlot();// 获取所有寄生绘图在宿主绘图上调用QListQwtPlot*parasiteshostPlot-parasitePlots();// 获取所有绘图列表宿主 寄生在任何绘图上调用均可constQListQwtPlot*plotListplot-plotList();// plotList[0] 是宿主绘图本身for(QwtPlot*p:plotList){constQwtPlotItemListitemsp-itemList();for(QwtPlotItem*item:items){// 遍历所有绘图项}}注意一个绘图不会既是寄生绘图又是宿主绘图。在寄生绘图上调用createParasitePlot()将返回nullptr。二、QwtFigure —— 多绘图布局容器2.1 类似 matplotlib Figure 的概念如果说寄生绘图解决的是一个画布上多套坐标轴的问题那么QwtFigure解决的就是一个窗口里多个子图的问题。QwtFigure是一个类似 matplotlibFigure的容器窗口用于组织和管理多个QwtPlot组件。它提供了两种布局方式归一化坐标布局和网格布局。2.2 归一化坐标布局归一化坐标就是以[0, 1]范围的百分比来定位子图遵循 Qt 标准的左上角坐标系QwtFigure*figurenewQwtFigure();figure-setSizeInches(8,6);// 设置图形尺寸为 8x6 英寸figure-setFaceColor(Qt::white);// 设置背景色QwtPlot*plot1newQwtPlot();// 添加到左上角四分之一区域figure-addAxes(plot1,QRectF(0.0,0.0,0.5,0.5));// 或者使用分离参数形式QwtPlot*plot2newQwtPlot();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-basedQwtPlot*plot3newQwtPlot();figure-addGridAxes(plot3,3,2,1,0);// 3x2 网格第1行第1列QwtPlot*plot4newQwtPlot();figure-addGridAxes(plot4,3,2,1,1);// 3x2 网格第2行跨2列QwtPlot*hostPlotnewQwtPlot();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 交互式操作蒙版QwtFigureWidgetOverlayQwtFigure 还提供了一个交互式操作蒙版QwtFigureWidgetOverlay允许用户在运行时通过鼠标拖拽来调整子绘图的位置和大小类似于可视化设计器中的操作体验。QwtFigure*figurenewQwtFigure();// ... 添加子绘图 ...// 创建并显示操作蒙版QwtFigureWidgetOverlay*overlaynewQwtFigureWidgetOverlay(figure);overlay-show();运行效果如下蒙版提供两项核心功能可通过枚举控制enumBuiltInFunctionsFlag{FunSelectCurrentPlot1,// 选择当前绘图FunResizePlot2// 调整绘图大小};启用/禁用特定功能// 只保留选择功能禁用尺寸调整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*layoutplot-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.xQwt 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/QWTGitee 镜像https://gitee.com/czyt1988/QWT在线文档https://czyt1988.github.io/QWT/zh/系列总述https://blog.csdn.net/czyt1988/article/details/160193393