【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 默认就使用线性引擎。

cpp 复制代码
#include <QwtPlot>

QwtPlot* plot = new QwtPlot();

// 默认使用线性刻度引擎,启用自动刻度计算
plot->setAxisAutoScale(QwtAxis::XBottom);

// 设置主刻度数量上限(最多10个主刻度)
plot->setAxisMaxMajor(QwtAxis::XBottom, 10);

// 设置次刻度数量上限(每个主刻度区间最多5个次刻度)
plot->setAxisMaxMinor(QwtAxis::XBottom, 5);

刻度引擎会自动在 1、2、5、10 等美观间隔中选择最合适的刻度间距,你只需要设定大致的刻度数量上限即可。

下图是刻度引擎的演示效果,展示了不同引擎和参数下的刻度表现:

1.3 对数刻度引擎

当数据跨越多个数量级时,线性刻度会让小数值"挤"在一起看不清。对数刻度引擎将坐标轴按对数分布,适合频率响应图、地震图等场景。

cpp 复制代码
#include <QwtLogScaleEngine>

// 为Y轴设置对数刻度引擎
plot->setAxisScaleEngine(QwtAxis::YLeft, new QwtLogScaleEngine());

// 设置对数刻度范围(跨越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 日期时间刻度引擎

处理时间序列数据时,日期刻度引擎能根据时间跨度自动选择合适的刻度级别------跨度大的用年份,跨度小的用小时甚至分钟。

cpp 复制代码
#include <QwtDateScaleEngine>
#include <QwtDateScaleDraw>
#include <QwtDate>

// 设置日期刻度引擎
plot->setAxisScaleEngine(QwtAxis::XBottom, new QwtDateScaleEngine());

// 设置日期刻度绘制器,自定义不同级别的日期格式
QwtDateScaleDraw* dateDraw = new QwtDateScaleDraw();
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 自定义刻度分割

如果自动计算无法满足需求,你可以完全手动控制刻度位置:

cpp 复制代码
#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 (int i = 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 反转刻度方向
cpp 复制代码
// 获取刻度引擎并设置属性
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

cpp 复制代码
// 设置坐标轴标题
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

cpp 复制代码
// 获取特定位置的坐标轴控件
QwtScaleWidget* scaleWidget = plot->axisWidget(QwtAxis::XBottom);

// 设置刻度标签字体
scaleWidget->setFont(QFont("Arial", 8));

// 自定义轴标题样式
QwtText title("时间 (s)");
title.setFont(QFont("Arial", 10, QFont::Bold));
title.setColor(Qt::darkBlue);
scaleWidget->setTitle(title);

// 设置刻度颜色
scaleWidget->setPalette(QPalette(Qt::black));

2.3 刻度标签格式化

默认情况下,刻度标签直接显示数值。如果你需要自定义格式(比如加单位、控制小数位数),可以继承 QwtScaleDraw 并重写 label() 方法:

cpp 复制代码
#include <QwtScaleDraw>

// 自定义刻度绘制器
class MyScaleDraw : public QwtScaleDraw
{
public:
    virtual QwtText label(double value) const override
    {
        // 保留1位小数
        return QwtText(QString::number(value, 'f', 1));
    }
};

// 应用自定义刻度绘制器
scaleWidget->setScaleDraw(new MyScaleDraw());

这个技巧非常实用。比如你想在温度轴上显示 "36.5°C",在百分比轴上显示 "85%",都可以通过重写 label() 方法实现。

2.4 颜色条(光谱图专用)

QwtScaleWidget 还支持显示颜色条,常用于光谱图(Spectrogram)等需要颜色映射的场景:

cpp 复制代码
// 启用颜色条
scaleWidget->setColorBarEnabled(true);
scaleWidget->setColorBarWidth(20);

// 设置颜色映射(蓝色到红色的渐变)
QwtLinearColorMap* colorMap = new QwtLinearColorMap(Qt::blue, Qt::red);
scaleWidget->setColorMap(QwtInterval(0, 100), colorMap);

2.5 内置交互功能(Qwt 7.0 新增)

Qwt 7.0 为坐标轴控件新增了内置交互功能,支持直接在坐标轴上拖动平移和滚轮缩放:

cpp 复制代码
// 启用内置平移功能(左键拖动)
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 基本使用

cpp 复制代码
#include <QwtPlotGrid>

QwtPlotGrid* grid = new QwtPlotGrid();

// 启用主网格(默认已启用)
grid->enableX(true);   // X轴主网格(垂直线)
grid->enableY(true);   // Y轴主网格(水平线)

// 启用次网格(默认关闭)
grid->enableXMin(false);
grid->enableYMin(false);

// 附加到绘图
grid->attach(plot);

网格线会自动跟随坐标轴刻度的变化------当你缩放或平移绘图时,网格线会自动调整位置,无需手动维护。

3.2 网格线样式

主网格和次网格可以设置不同的样式,通常主网格更醒目,次网格更淡:

cpp 复制代码
// 主网格:灰色实线
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 完整网格配置示例

下面是一个包含网格、曲线的完整示例:

cpp 复制代码
#include <QwtPlot>
#include <QwtPlotGrid>
#include <QwtPlotCurve>
#include <QwtLegend>

QwtPlot* plot = new QwtPlot();
plot->setTitle("网格配置示例");
plot->setCanvasBackground(Qt::white);

// 创建并配置网格
QwtPlotGrid* grid = new QwtPlotGrid();
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 = new QwtPlotCurve("信号");
QPolygonF points;
for (int i = 0; i <= 100; i++) {
    double x = i;
    double y = 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 外部图例

外部图例独立于画布,占用绘图区域的边缘空间:

cpp 复制代码
#include <QwtLegend>

// 创建并插入图例(放在右侧)
QwtLegend* legend = new QwtLegend();
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 图例交互

默认情况下,点击图例项可以切换对应曲线的显示/隐藏状态,这是非常实用的交互功能:

cpp 复制代码
// 可点击模式(默认)------点击切换曲线显示
legend->setItemMode(QwtLegend::ClickableItem);

// 只读模式------仅展示,不可点击
// legend->setItemMode(QwtLegend::ReadOnlyItem);

4.3 绘图项的图例属性

每条曲线可以控制自己在图例中的显示方式:

cpp 复制代码
QwtPlotCurve* curve = new QwtPlotCurve("曲线A");

// 控制图例图标显示内容
curve->setLegendAttribute(QwtPlotCurve::LegendShowLine, true);    // 显示线条
curve->setLegendAttribute(QwtPlotCurve::LegendShowSymbol, true);  // 显示符号
curve->setLegendAttribute(QwtPlotCurve::LegendShowBrush, true);   // 显示填充

// 设置图例图标大小
curve->setLegendIconSize(QSize(20, 15));

4.4 内嵌图例

如果不想让图例占用绘图边缘的空间,可以使用 QwtPlotLegendItem 将图例直接绘制在画布上:

cpp 复制代码
#include <QwtPlotLegendItem>

// 创建内嵌图例(作为绘图项附加到画布)
QwtPlotLegendItem* legendItem = new QwtPlotLegendItem();
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 非常简洁,一个方法搞定:

cpp 复制代码
#include <QwtPlot>

// 设置 YLeft 轴刻度朝内
plot->setAxisTickDirection(QwtAxis::YLeft, QwtPlot::TickInside);

// 设置 XBottom 轴刻度朝内
plot->setAxisTickDirection(QwtAxis::XBottom, QwtPlot::TickInside);

// 刷新显示
plot->replot();

枚举值说明:

枚举值 说明
QwtPlot::TickOutside 刻度朝外(默认行为)
QwtPlot::TickInside 刻度朝内,从画布边缘向内延伸

下图展示了刻度朝内的实际效果:

5.3 独立控制与批量设置

每个坐标轴可以独立设置刻度方向,也可以批量设置:

cpp 复制代码
// 所有轴刻度朝内
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 的坐标轴系统只支持固定四个轴(yLeftyRightxBottomxTop),每个位置只能有一个轴。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 的坐标轴与刻度系统:

  1. 刻度引擎 是坐标轴的核心------线性引擎适合常规数据,对数引擎适合大范围数据,日期引擎适合时间序列。特殊需求可以手动创建 QwtScaleDiv 完全控制刻度位置。

  2. 坐标轴控件提供了从标题、字体到刻度标签格式的全面自定义能力。Qwt 7.0 还新增了坐标轴内置交互功能。

  3. 网格线自动跟随刻度变化,主/次网格可独立控制样式,是提升图表可读性的利器。

  4. 图例系统支持外部图例和内嵌图例两种方式,点击图例项可切换曲线显示状态。

  5. 刻度朝内显示 是 Qwt 7.2.1+ 的新功能,通过简单的 setAxisTickDirection() 调用即可实现紧凑布局效果。

  6. QwtAxisId 多轴架构是 Qwt 7.0 的核心改进之一,突破了原版四轴限制,支持任意多轴。

系列文章

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


相关链接

相关推荐
sycmancia3 小时前
Qt——多线程间的互斥
开发语言·qt
尘中远9 小时前
【Qwt 7.0 系列】常用图表类型实战 —— 柱状图、散点图、箱线图与直方图
qt·qwt·工业软件·科学绘图
尘中远9 小时前
【Qwt 7.0 系列】交互功能详解 —— 平移、缩放、坐标轴交互与数据拾取
qt·数据可视化·绘图·qcustomplot·qwt·科学绘图
sycmancia9 小时前
Qt——进程与线程的概念
qt
郝学胜-神的一滴10 小时前
Qt 高级编程 034:深耕QWidget底层内核—彻底吃透无边框窗口设计核心原理
开发语言·c++·qt·程序人生·软件开发·用户界面
尘中远10 小时前
【Qwt 7.0 系列】3D 数据可视化 —— OpenGL 高性能三维绘图
qt·3d·qcustomplot·qwt·科学绘图·高性能绘图
满天星830357711 小时前
【Qt】控件(二) (geometry及与frameGeometry的区别)
开发语言·qt
大气的小蜜蜂11 小时前
基于Python+PyQt5+SQLite的药房管理系统实现:事务一致性与界面解耦全流程解析
python·qt·sqlite
尘中远11 小时前
【Qwt 7.0 系列】总体架构解析 —— 从单体到三库模块化的演进
qt·matplotlib·绘图·qwt·科学绘图