【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 = new QwtPlot();
// ... 设置宿主绘图的参数(曲线、标题等)

////////////////////////////////////////////////////////
// 添加寄生坐标系
////////////////////////////////////////////////////////
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");

// 在寄生绘图上添加曲线
QColor curColor(255, 127, 14);  // 橙色,与宿主曲线区分
QwtPlotCurve* parasiteCurve = new QwtPlotCurve("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);

运行效果如下(单寄生绘图):

01-parasite-01

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");

// 在第二个寄生绘图上添加曲线
QColor curColor2(192, 43, 149);  // 紫色
QwtPlotCurve* parasiteCurve2 = new QwtPlotCurve("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);

两个寄生绘图叠加后的效果:

02-parasite-plot

可以看到,图上有三套不同颜色的坐标轴,分别对应宿主绘图和两个寄生绘图。这在原版 Qwt 6.x 中是根本做不到的。

1.4 寄生绘图的层级关系

寄生绘图有明确的层级关系,决定了坐标轴的布局顺序:

  • 先添加的寄生绘图处于低层级,其坐标轴靠近宿主绘图
  • 后添加的寄生绘图处于高层级,其坐标轴远离宿主绘图

布局时,宿主绘图先完成自身布局,然后从低层级到高层级依次布局寄生绘图的坐标轴。

1.5 坐标轴共享配置

通过 setParasiteShareAxis() 方法,可以让寄生绘图与宿主绘图共享某个坐标轴。共享后,该轴的刻度范围会自动同步:

// 寄生绘图的 X 轴与宿主共享
parasitePlot->setParasiteShareAxis(QwtAxis::XBottom);

这在 X 轴代表时间等公共维度时非常有用——多个 Y 轴各自独立,但共享同一条 X 轴。

1.6 生命周期管理与遍历

生命周期:寄生绘图的生命周期与宿主绘图绑定。宿主绘图销毁时,所有寄生绘图自动销毁,无需手动管理。

判断与遍历:通过以下方法可以区分和获取绘图对象:

// 判断是否为宿主/寄生绘图
bool isHost = plot->isHostPlot();
bool isParasite = plot->isParasitePlot();

// 获取宿主绘图(在寄生绘图上调用)
QwtPlot* host = parasitePlot->hostPlot();

// 获取所有寄生绘图(在宿主绘图上调用)
QList<QwtPlot*> parasites = hostPlot->parasitePlots();

// 获取所有绘图列表(宿主 + 寄生,在任何绘图上调用均可)
const QList<QwtPlot*> plotList = plot->plotList();
// plotList[0] 是宿主绘图本身
for (QwtPlot* p : plotList) {
    const QwtPlotItemList& items = p->itemList();
    for (QwtPlotItem* item : items) {
        // 遍历所有绘图项
    }
}

注意:一个绘图不会既是寄生绘图又是宿主绘图。在寄生绘图上调用 createParasitePlot() 将返回 nullptr


二、QwtFigure —— 多绘图布局容器

2.1 类似 matplotlib Figure 的概念

如果说寄生绘图解决的是"一个画布上多套坐标轴"的问题,那么 QwtFigure 解决的就是"一个窗口里多个子图"的问题。

QwtFigure 是一个类似 matplotlib Figure 的容器窗口,用于组织和管理多个 QwtPlot 组件。它提供了两种布局方式:归一化坐标布局和网格布局。

2.2 归一化坐标布局

归一化坐标就是以 [0, 1] 范围的百分比来定位子图,遵循 Qt 标准的左上角坐标系:

QwtFigure* figure = new QwtFigure();
figure->setSizeInches(8, 6);      // 设置图形尺寸为 8x6 英寸
figure->setFaceColor(Qt::white);  // 设置背景色

QwtPlot* plot1 = new QwtPlot();
// 添加到左上角四分之一区域
figure->addAxes(plot1, QRectF(0.0, 0.0, 0.5, 0.5));

// 或者使用分离参数形式
QwtPlot* plot2 = new QwtPlot();
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 = new QwtPlot();
figure->addGridAxes(plot3, 3, 2, 1, 0);

// 3x2 网格,第1行第1列
QwtPlot* plot4 = new QwtPlot();
figure->addGridAxes(plot4, 3, 2, 1, 1);

// 3x2 网格,第2行,跨2列
QwtPlot* hostPlot = new QwtPlot();
figure->addGridAxes(hostPlot, 3, 2, 2, 0, 1, 2);

addGridAxes 的参数含义:(plot, totalRows, totalCols, row, col, rowSpan, colSpan),其中 rowSpancolSpan 可选,用于跨行跨列。

下面是一个综合使用归一化坐标和网格布局的完整示例效果:

03-qwt_figure

2.4 坐标轴对齐功能

当多个子图的 Y 轴刻度范围差异较大时,画布的水平边界可能不对齐,视觉上不够整齐:

04-figure-scale-not-aligment

QwtFigure 提供了 addAxisAlignment() 方法来解决这个问题:

// plot1、plot3、hostPlot 的左坐标轴对齐
figure->addAxisAlignment({ plot1, plot3, hostPlot }, QwtAxis::YLeft);

// plot2、plot4 的左坐标轴对齐
figure->addAxisAlignment({ plot2, plot4 }, QwtAxis::YLeft);

对齐后,相关子图的画布边界会自动调整到同一水平线:

05-figure-scale-aligment

注意:只有可见的坐标轴才能对齐。如果某个子图的对应坐标轴未显示,则无法参与对齐。

2.5 交互式操作蒙版:QwtFigureWidgetOverlay

QwtFigure 还提供了一个交互式操作蒙版 QwtFigureWidgetOverlay,允许用户在运行时通过鼠标拖拽来调整子绘图的位置和大小,类似于可视化设计器中的操作体验。

QwtFigure* figure = new QwtFigure();
// ... 添加子绘图 ...

// 创建并显示操作蒙版
QwtFigureWidgetOverlay* overlay = new QwtFigureWidgetOverlay(figure);
overlay->show();

运行效果如下:

06-figure-widget-overlay

蒙版提供两项核心功能,可通过枚举控制:

enum BuiltInFunctionsFlag {
    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.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 的两大布局利器:

  1. 寄生绘图:通过 createParasitePlot() 在同一画布上叠加任意多套独立坐标轴,完美解决多 Y 轴、多量级数据的可视化需求。寄生绘图与宿主绘图共享画布、独立坐标系、生命周期自动管理。

  2. QwtFigure 容器:类似 matplotlib Figure 的多绘图布局容器,支持归一化坐标定位和网格布局,还能通过 addAxisAlignment() 实现子图坐标轴对齐,配合 QwtFigureWidgetOverlay 蒙版实现交互式布局调整。

这两个功能让 Qwt 7.0 在多轴、多图场景下的能力大幅跃升,从"够用"走向"好用"。

系列文章

系列总述:Qwt 7.0 —— 基于 Qt 的高性能 2D/3D 绘图库


相关链接

  • 项目地址: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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尘中远

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值