【Qwt 7.0 系列】坐标轴与刻度系统 —— 刻度引擎、网格、图例与刻度朝内
【Qwt 7.0 系列】坐标轴与刻度系统 —— 刻度引擎、网格、图例与刻度朝内
本文是 Qwt 7.0 系列介绍和教程,如果你正在寻找一个高性能、协议友好、同时支持 2D 和 3D 绘图的 Qt 数据可视化库,那么这篇文章就是为你准备的。
系列总述文章:Qwt 7.0 —— 基于 Qt 的高性能 2D/3D 绘图库
概述 | 高性能曲线绘制 | 常用图表类型 | 高级科学图表 | 多坐标轴与布局 | 交互功能 | 3D 数据可视化 | 坐标轴与刻度 | 控件与辅助元素 | 总体架构解析 | matplotlib 风格绘图
项目地址:GitHub | Gitee | 在线文档
引言
坐标轴是数据可视化的骨架。无论你的曲线画得多漂亮,如果坐标轴的刻度不合理、标签看不清、网格太杂乱,读者就无法准确理解数据。Qwt 7.0 作为基于原版 Qwt 6.2.0 的现代化维护分支,提供了一套强大的刻度引擎和灵活的坐标轴配置系统,能够应对从简单的线性图表到复杂的时间序列、对数坐标等各种场景。
本篇是 Qwt 7.0 系列博客的第 8 篇,将全面介绍 Qwt 7.0 的坐标轴与刻度系统,包括:
- 刻度引擎:线性、对数、日期时间三种引擎的原理与用法
- 坐标轴控件:刻度线配置、标签格式化、轴标题与外观
- 网格线:主/次网格的独立控制与样式配置
- 图例系统:外部图例与内嵌图例的使用
- 刻度朝内显示:Qwt 7.2.1+ 新增的紧凑布局功能
一、刻度引擎 QwtScaleEngine
刻度引擎是坐标轴系统的"大脑"。它负责根据数据范围自动计算合理的刻度位置——比如数据范围是 0~100,引擎会自动选择 0、20、40、60、80、100 作为主刻度,而不是 0、17、34 这样的奇怪数字。
1.1 三种内置刻度引擎
Qwt 7.0 提供三种内置刻度引擎:
| 引擎类 | 适用场景 | 主刻度示例 |
|---|---|---|
QwtLinearScaleEngine | 线性数据(默认) | 0, 20, 40, 60, 80, 100 |
QwtLogScaleEngine | 大范围/指数关系数据 | 1, 10, 100, 1000 |
QwtDateScaleEngine | 时间序列数据 | 按时间级别自动选择 |
1.2 线性刻度引擎(默认)
线性刻度引擎是默认引擎,适用于大多数均匀分布的数据。你甚至不需要显式创建它——QwtPlot默认就使用线性引擎。
#include<QwtPlot>QwtPlot*plot=newQwtPlot();// 默认使用线性刻度引擎,启用自动刻度计算plot->setAxisAutoScale(QwtAxis::XBottom);// 设置主刻度数量上限(最多10个主刻度)plot->setAxisMaxMajor(QwtAxis::XBottom,10);// 设置次刻度数量上限(每个主刻度区间最多5个次刻度)plot->setAxisMaxMinor(QwtAxis::XBottom,5);刻度引擎会自动在 1、2、5、10 等美观间隔中选择最合适的刻度间距,你只需要设定大致的刻度数量上限即可。
下图是刻度引擎的演示效果,展示了不同引擎和参数下的刻度表现:
1.3 对数刻度引擎
当数据跨越多个数量级时,线性刻度会让小数值"挤"在一起看不清。对数刻度引擎将坐标轴按对数分布,适合频率响应图、地震图等场景。
#include<QwtLogScaleEngine>// 为Y轴设置对数刻度引擎plot->setAxisScaleEngine(QwtAxis::YLeft,newQwtLogScaleEngine());// 设置对数刻度范围(跨越6个数量级)plot->setAxisScale(QwtAxis::YLeft,0.001,1000);对数刻度的特点:
- 主刻度位于 10^n 位置(0.001, 0.01, 0.1, 1, 10, 100, 1000…)
- 次刻度位于中间位置(2, 3, 4, 5, 6, 7, 8, 9 对应倍数)
1.4 日期时间刻度引擎
处理时间序列数据时,日期刻度引擎能根据时间跨度自动选择合适的刻度级别——跨度大的用年份,跨度小的用小时甚至分钟。
#include<QwtDateScaleEngine>#include<QwtDateScaleDraw>#include<QwtDate>// 设置日期刻度引擎plot->setAxisScaleEngine(QwtAxis::XBottom,newQwtDateScaleEngine());// 设置日期刻度绘制器,自定义不同级别的日期格式QwtDateScaleDraw*dateDraw=newQwtDateScaleDraw();dateDraw->setDateFormat(QwtDateScaleDraw::Month,QString("yyyy-MM"));// 月份级别显示年月dateDraw->setDateFormat(QwtDateScaleDraw::Day,QString("MM-dd"));// 日期级别显示月日plot->setAxisScaleDraw(QwtAxis::XBottom,dateDraw);// 设置时间范围(QwtDate::toDouble 将 QDateTime 转为轴坐标值)QDateTime start=QDateTime::fromString("2024-01-01","yyyy-MM-dd");QDateTime end=QDateTime::fromString("2024-12-31","yyyy-MM-dd");plot->setAxisScale(QwtAxis::XBottom,QwtDate::toDouble(start),QwtDate::toDouble(end));QwtDateScaleDraw支持的时间级别格式如下:
| 级别 | 说明 | 默认格式 |
|---|---|---|
Millisecond | 毫秒级 | hh:mm:ss.zzz |
Second | 秒级 | hh:mm:ss |
Minute | 分钟级 | hh:mm |
Hour | 小时级 | hh:mm |
Day | 日级 | MM-dd |
Week | 周级 | MM-dd |
Month | 月级 | yyyy-MM |
Year | 年级 | yyyy |
引擎会根据数据范围自动选择最合适的级别。比如数据跨度是半年,它会选 Day 或 Week 级别;如果是十年,就选 Year 级别。
1.5 自定义刻度分割
如果自动计算无法满足需求,你可以完全手动控制刻度位置:
#include<QwtScaleDiv>// 手动创建刻度划分QwtScaleDiv scaleDiv;scaleDiv.setInterval(0,100);// 设置范围// 设置主刻度QList<double>majorTicks;majorTicks<<0<<20<<40<<60<<80<<100;scaleDiv.setTicks(QwtScaleDiv::MajorTick,majorTicks);// 设置次刻度QList<double>minorTicks;for(inti=0;i<=100;i+=4){if(!majorTicks.contains(i))minorTicks<<i;}scaleDiv.setTicks(QwtScaleDiv::MinorTick,minorTicks);// 应用自定义刻度划分plot->setAxisScaleDiv(QwtAxis::XBottom,scaleDiv);1.6 刻度引擎属性
刻度引擎还提供了一些属性来微调刻度计算行为:
| 属性 | 说明 |
|---|---|
IncludeReference | 强制包含参考值作为刻度 |
Symmetric | 强制范围对称(如 -100 ~ 100) |
Floating | 端点不强制落在刻度上 |
Inverted | 反转刻度方向 |
// 获取刻度引擎并设置属性QwtScaleEngine*engine=plot->axisScaleEngine(QwtAxis::XBottom);engine->setAttribute(QwtScaleEngine::IncludeReference,true);engine->setAttribute(QwtScaleEngine::Symmetric,false);engine->setAttribute(QwtScaleEngine::Floating,false);// 设置边距(数据范围两端各留5%空白)engine->setMargins(0.05);小贴士:选择刻度引擎的简单原则——线性数据用线性引擎(默认),跨数量级数据用对数引擎,时间数据用日期引擎。特殊需求手动设置刻度划分。
二、坐标轴控件 QwtScaleWidget
QwtScaleWidget是坐标轴的显示控件,负责绘制刻度线、刻度标签和轴标题。在QwtPlot中,每个坐标轴都对应一个QwtScaleWidget实例。
2.1 通过 QwtPlot 配置坐标轴
日常使用中,你通常通过QwtPlot的便捷方法来配置坐标轴,而不需要直接操作QwtScaleWidget:
// 设置坐标轴标题plot->setAxisTitle(QwtAxis::XBottom,"时间 (s)");plot->setAxisTitle(QwtAxis::YLeft,"电压 (V)");// 设置坐标轴范围plot->setAxisScale(QwtAxis::XBottom,0,100);plot->setAxisScale(QwtAxis::YLeft,-10,10);// 控制坐标轴可见性(隐藏顶部X轴,显示右侧Y轴)plot->setAxisVisible(QwtAxis::XTop,false);plot->setAxisVisible(QwtAxis::YRight,true);plot->replot();Qwt 7.0 使用QwtAxis::Position枚举标识四个基本坐标轴位置:
| 枚举值 | 说明 |
|---|---|
QwtAxis::XBottom | 底部水平轴 |
QwtAxis::XTop | 顶部水平轴 |
QwtAxis::YLeft | 左侧垂直轴 |
QwtAxis::YRight | 右侧垂直轴 |
2.2 获取并自定义坐标轴控件
需要更精细的控制时,可以通过axisWidget()获取底层的QwtScaleWidget:
// 获取特定位置的坐标轴控件QwtScaleWidget*scaleWidget=plot->axisWidget(QwtAxis::XBottom);// 设置刻度标签字体scaleWidget->setFont(QFont("Arial",8));// 自定义轴标题样式QwtTexttitle("时间 (s)");title.setFont(QFont("Arial",10,QFont::Bold));title.setColor(Qt::darkBlue);scaleWidget->setTitle(title);// 设置刻度颜色scaleWidget->setPalette(QPalette(Qt::black));2.3 刻度标签格式化
默认情况下,刻度标签直接显示数值。如果你需要自定义格式(比如加单位、控制小数位数),可以继承QwtScaleDraw并重写label()方法:
#include<QwtScaleDraw>// 自定义刻度绘制器classMyScaleDraw:publicQwtScaleDraw{public:virtualQwtTextlabel(doublevalue)constoverride{// 保留1位小数returnQwtText(QString::number(value,'f',1));}};// 应用自定义刻度绘制器scaleWidget->setScaleDraw(newMyScaleDraw());这个技巧非常实用。比如你想在温度轴上显示 “36.5°C”,在百分比轴上显示 “85%”,都可以通过重写label()方法实现。
2.4 颜色条(光谱图专用)
QwtScaleWidget还支持显示颜色条,常用于光谱图(Spectrogram)等需要颜色映射的场景:
// 启用颜色条scaleWidget->setColorBarEnabled(true);scaleWidget->setColorBarWidth(20);// 设置颜色映射(蓝色到红色的渐变)QwtLinearColorMap*colorMap=newQwtLinearColorMap(Qt::blue,Qt::red);scaleWidget->setColorMap(QwtInterval(0,100),colorMap);2.5 内置交互功能(Qwt 7.0 新增)
Qwt 7.0 为坐标轴控件新增了内置交互功能,支持直接在坐标轴上拖动平移和滚轮缩放:
// 启用内置平移功能(左键拖动)scaleWidget->setBuiltInAction(QwtScaleWidget::Pan,true);// 启用内置缩放功能scaleWidget->setBuiltInAction(QwtScaleWidget::Zoom,true);// 设置交互的鼠标按钮scaleWidget->setActionButton(Qt::LeftButton);// 左键拖动平移这是 Qwt 7.0 相比原版 Qwt 6.2.0 的一项实用改进——用户不需要编写额外的事件过滤器,就能直接在坐标轴上拖动来浏览数据。
三、网格 QwtPlotGrid
网格线帮助读者更准确地判断数据点的坐标值,是科学图表中不可或缺的辅助元素。QwtPlotGrid是 Qwt 提供的网格绘图项,它会自动跟随坐标轴的刻度划分来绘制网格线。
3.1 基本使用
#include<QwtPlotGrid>QwtPlotGrid*grid=newQwtPlotGrid();// 启用主网格(默认已启用)grid->enableX(true);// X轴主网格(垂直线)grid->enableY(true);// Y轴主网格(水平线)// 启用次网格(默认关闭)grid->enableXMin(false);grid->enableYMin(false);// 附加到绘图grid->attach(plot);网格线会自动跟随坐标轴刻度的变化——当你缩放或平移绘图时,网格线会自动调整位置,无需手动维护。
3.2 网格线样式
主网格和次网格可以设置不同的样式,通常主网格更醒目,次网格更淡:
// 主网格:灰色实线grid->setMajorPen(QPen(QColor(150,150,150),1.0,Qt::SolidLine));// 次网格:浅灰色点线grid->setMinorPen(QPen(QColor(200,200,200),0.5,Qt::DotLine));// 也可以用快捷方法统一设置// grid->setPen(Qt::gray, 0.5, Qt::DotLine);小技巧:在 Qt 中,线宽设为
0.0表示使用 1 像素的快速绘制线(cosmetic pen),对于网格线来说这能获得最细的线条效果。
3.3 完整网格配置示例
下面是一个包含网格、曲线的完整示例:
#include<QwtPlot>#include<QwtPlotGrid>#include<QwtPlotCurve>#include<QwtLegend>QwtPlot*plot=newQwtPlot();plot->setTitle("网格配置示例");plot->setCanvasBackground(Qt::white);// 创建并配置网格QwtPlotGrid*grid=newQwtPlotGrid();grid->enableX(true);grid->enableY(true);grid->enableXMin(true);// 启用次网格grid->enableYMin(true);// 主网格用灰色实线,次网格用浅灰色点线grid->setMajorPen(QPen(QColor(150,150,150),1.0,Qt::SolidLine));grid->setMinorPen(QPen(QColor(220,220,220),0.0,Qt::DotLine));grid->attach(plot);// 添加一条正弦曲线QwtPlotCurve*curve=newQwtPlotCurve("信号");QPolygonF points;for(inti=0;i<=100;i++){doublex=i;doubley=50+30*std::sin(i*0.1);points<<QPointF(x,y);}curve->setSamples(points);curve->setPen(QPen(Qt::blue,2.0));curve->attach(plot);plot->setAxisScale(QwtAxis::XBottom,0,100);plot->setAxisScale(QwtAxis::YLeft,0,100);plot->replot();3.4 网格核心方法速查
| 方法 | 说明 |
|---|---|
enableX(bool) | 启用/禁用 X 轴主网格 |
enableY(bool) | 启用/禁用 Y 轴主网格 |
enableXMin(bool) | 启用/禁用 X 轴次网格 |
enableYMin(bool) | 启用/禁用 Y 轴次网格 |
setMajorPen() | 设置主网格画笔 |
setMinorPen() | 设置次网格画笔 |
setXDiv() | 手动设置 X 轴刻度划分 |
setYDiv() | 手动设置 Y 轴刻度划分 |
四、图例 QwtLegend
图例用于标识不同曲线或数据系列的含义,是多人阅读图表时的必备元素。Qwt 7.0 提供了两种图例方式:外部图例(QwtLegend)和内嵌图例(QwtPlotLegendItem)。
4.1 外部图例
外部图例独立于画布,占用绘图区域的边缘空间:
#include<QwtLegend>// 创建并插入图例(放在右侧)QwtLegend*legend=newQwtLegend();plot->insertLegend(legend,QwtPlot::RightLegend);// 其他位置选项:// QwtPlot::LeftLegend - 左侧// QwtPlot::BottomLegend - 底部// QwtPlot::TopLegend - 顶部// 第三个参数控制图例占比(0.0~1.0)// plot->insertLegend(legend, QwtPlot::RightLegend, 0.2); // 占20%宽度下图是图例演示效果,展示了多条曲线的图例显示:
4.2 图例交互
默认情况下,点击图例项可以切换对应曲线的显示/隐藏状态,这是非常实用的交互功能:
// 可点击模式(默认)——点击切换曲线显示legend->setItemMode(QwtLegend::ClickableItem);// 只读模式——仅展示,不可点击// legend->setItemMode(QwtLegend::ReadOnlyItem);4.3 绘图项的图例属性
每条曲线可以控制自己在图例中的显示方式:
QwtPlotCurve*curve=newQwtPlotCurve("曲线A");// 控制图例图标显示内容curve->setLegendAttribute(QwtPlotCurve::LegendShowLine,true);// 显示线条curve->setLegendAttribute(QwtPlotCurve::LegendShowSymbol,true);// 显示符号curve->setLegendAttribute(QwtPlotCurve::LegendShowBrush,true);// 显示填充// 设置图例图标大小curve->setLegendIconSize(QSize(20,15));4.4 内嵌图例
如果不想让图例占用绘图边缘的空间,可以使用QwtPlotLegendItem将图例直接绘制在画布上:
#include<QwtPlotLegendItem>// 创建内嵌图例(作为绘图项附加到画布)QwtPlotLegendItem*legendItem=newQwtPlotLegendItem();legendItem->attach(plot);// 设置位置legendItem->setPosition(QwtPlotLegendItem::TopRight);// 右上角// 设置背景和边框legendItem->setBackgroundBrush(QBrush(Qt::white));legendItem->setBorderPen(QPen(Qt::gray,1));// 设置最大列数legendItem->setMaxColumns(1);内嵌图例的好处是它"浮"在画布上方,不占用布局空间,适合空间紧凑的场景。
4.5 图例使用建议
| 曲线数量 | 推荐方案 |
|---|---|
| 少量(<5条) | 右侧外部图例 |
| 中等(5-10条) | 底部外部图例 |
| 大量(>10条) | 内嵌图例或分组显示 |
五、刻度朝内显示(7.2.1+ 新增)
这是 Qwt 7.0 系列中一个虽小但实用的功能。从 7.2.1 版本开始,Qwt 支持将坐标轴刻度线显示在绘图区域内部,而非传统的朝外显示。
5.1 什么是刻度朝内
传统绘图中,刻度线从轴线向外延伸。而刻度朝内功能将刻度线从画布边缘向内延伸,刻度标签仍然保持在外部:
刻度朝外(默认): 刻度朝内: ────┼────┼────┼────→ ────┼────┼────┼────→ │ │ │ │ │ │ ← 向内延伸 0 2 4 0 2 4 ┌────────────────┐ │ ← 刻度伸入画布 │ └────────────────┘5.2 使用方法
API 非常简洁,一个方法搞定:
#include<QwtPlot>// 设置 YLeft 轴刻度朝内plot->setAxisTickDirection(QwtAxis::YLeft,QwtPlot::TickInside);// 设置 XBottom 轴刻度朝内plot->setAxisTickDirection(QwtAxis::XBottom,QwtPlot::TickInside);// 刷新显示plot->replot();枚举值说明:
| 枚举值 | 说明 |
|---|---|
QwtPlot::TickOutside | 刻度朝外(默认行为) |
QwtPlot::TickInside | 刻度朝内,从画布边缘向内延伸 |
下图展示了刻度朝内的实际效果:
5.3 独立控制与批量设置
每个坐标轴可以独立设置刻度方向,也可以批量设置:
// 所有轴刻度朝内plot->setAxisTickDirection(QwtAxis::YLeft,QwtPlot::TickInside);plot->setAxisTickDirection(QwtAxis::YRight,QwtPlot::TickInside);plot->setAxisTickDirection(QwtAxis::XBottom,QwtPlot::TickInside);plot->setAxisTickDirection(QwtAxis::XTop,QwtPlot::TickInside);plot->replot();// 查询当前设置QwtPlot::TickDirection dir=plot->axisTickDirection(QwtAxis::YLeft);if(dir==QwtPlot::TickInside){qDebug()<<"当前刻度朝内";}5.4 适用场景
刻度朝内功能特别适合以下场景:
- 紧凑布局:在有限的窗口空间中最大化绘图区域
- 出版物图表:很多学术期刊偏好刻度朝内的风格
- 嵌入式界面:屏幕空间宝贵的嵌入式设备界面
注意:朝内刻度自动继承朝外刻度的样式设置(长度、线宽、颜色),无需重复配置。该功能还可与寄生轴(Parasite Plot)配合使用,每个寄生绘图可独立设置刻度方向。
六、与旧版本的区别
如果你从原版 Qwt 6.2.0 迁移到 Qwt 7.0,坐标轴系统有几个重要变化需要了解:
6.1 QwtAxisId 多轴架构(7.0 新增)
原版 Qwt 6.2.0 的坐标轴系统只支持固定四个轴(yLeft、yRight、xBottom、xTop),每个位置只能有一个轴。Qwt 7.0 引入了QwtAxisId架构,每个轴由Position + 序号组成,支持在同一位置放置任意多个坐标轴。
通过QwtPlot::createParasitePlot()可以创建寄生绘图,共享宿主画布区域但拥有独立的坐标轴。这意味着你可以同时在左侧放两个、三个甚至更多 Y 轴,彻底突破了原版"四轴限制"。
6.2 刻度朝内显示(7.2.1+ 新增)
原版 Qwt 6.2.0 不支持刻度朝内显示,刻度线只能朝外。Qwt 7.2.1+ 通过setAxisTickDirection()方法新增了这一功能。
6.3 坐标轴内置交互(7.0 新增)
原版 Qwt 6.2.0 的坐标轴控件不支持直接交互。Qwt 7.0 为QwtScaleWidget新增了内置平移和缩放功能,用户可以直接在坐标轴上拖动浏览数据。
6.4 兼容性
Qwt 7.0 保留了旧版的Axis枚举兼容(通过QWT_AXIS_COMPAT宏),原来的QwtPlot::yLeft等写法仍然可用,迁移成本很低。
| 特性 | 原版 Qwt 6.2.0 | Qwt 7.0+ |
|---|---|---|
| 坐标轴数量 | 固定4个 | 任意多轴(QwtAxisId) |
| 刻度朝内 | 不支持 | 7.2.1+ 支持 |
| 坐标轴交互 | 无 | 内置平移/缩放 |
| 旧枚举兼容 | - | 通过 QWT_AXIS_COMPAT 宏 |
七、总结
本篇全面介绍了 Qwt 7.0 的坐标轴与刻度系统:
刻度引擎是坐标轴的核心——线性引擎适合常规数据,对数引擎适合大范围数据,日期引擎适合时间序列。特殊需求可以手动创建
QwtScaleDiv完全控制刻度位置。坐标轴控件提供了从标题、字体到刻度标签格式的全面自定义能力。Qwt 7.0 还新增了坐标轴内置交互功能。
网格线自动跟随刻度变化,主/次网格可独立控制样式,是提升图表可读性的利器。
图例系统支持外部图例和内嵌图例两种方式,点击图例项可切换曲线显示状态。
刻度朝内显示是 Qwt 7.2.1+ 的新功能,通过简单的
setAxisTickDirection()调用即可实现紧凑布局效果。QwtAxisId 多轴架构是 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