18 KiB
Geopro 3.0 桌面客户端 — 数据集详情「二维图表」返工技术方案
版本 v1.0 · 适用范围:数据集详情页的「原数据(散点图)」与「网格数据(填充等值线图)」 状态:返工指令(推翻当前 QGraphicsView 实现,重做)
0. 给开发(含 AI 编码)的硬约束 — 先读这一段
- 不嵌入 QWebEngine,不引入 Chromium。 整个客户端的技术路线是 Qt C++ 原生,核心动机包含离线/现场作业能力;把核心数据可视化做成依赖 web 渲染会破坏该前提,禁止。
- 推翻当前的裸
QGraphicsView+ VTK 渲染窗口方案。 当前 9 个问题的根因是 2D 框架选错,不是"没嵌 web"。 - 改用
QwtPlot作为两类图的坐标系框架;等值线多边形用 VTK 算法(仅算法,不开 VTK 渲染窗口)。 - 验收标准 = 视觉等价 + 交互一致,不是字节级像素 diff。原生渲染与 web 端 Plotly 在抗锯齿、字体、亚像素上天生有别,任何工具都无法字节级相同;目标是"用户看不出降级感"。
- 不要自由发挥架构。 本文档把分层、类、职责定死,照做;遇到本文档未覆盖的细节,先问,不要自行另起一套。
1. 根因诊断(为什么会做崩)
当前实现把"图形 + 坐标轴 + 色阶图例"全部糊在一个 QGraphicsView 场景里一起做变换,导致系统性错误。裸 QGraphicsView 是通用图形框架,不具备"科学图表"语义——它不知道什么是固定坐标轴、什么是"仅数据区缩放"、什么是"图例不参与变换"。
把用户列的 9 个问题按根因归类:
| # | 问题 | 根因类别 | 由谁解决 |
|---|---|---|---|
| 1 | 色阶图随大图缩放、与横坐标重叠 | 框架(图例未独立) | QwtPlot 分层 |
| 3 | 拖动时坐标轴跟着动(应只动数据、轴值跟随) | 框架(无数据区/轴分离) | QwtPlot 内置交互 |
| 4 | 一拖动画面就花 | 框架(手写变换 + 重绘错误) | QwtPlot 内置交互 |
| 9 | 图比原版瘦(纵横比错) | 框架(无纵横比控制) | QwtPlot 纵横比锁定 |
| 2 | 配色与原版不一致(离散色带 vs 连续插值) | 实现细节(色阶映射) | QwtLinearColorMap |
| 5 | 等值线数字没显示 | 实现细节(缺标注) | 等值线 label |
| 6 | 原数据/网格数据切换样式被改错 | UI 布局(与图表无关) | 改回页签样式 |
| 7 | 显示异常/等值线开关跑到面板外 | UI 布局(与图表无关) | 归位工具条 |
| 8 | 下方缺「描述」页签 | UI 布局(与图表无关) | 补页签 |
关键结论:9 个问题没有一个需要"嵌 web"才能解决。第 1 类(#1/#3/#4/#9)换对框架即消失;第 2 类(#2/#5)是渲染细节;第 3 类(#6/#7/#8)是纯 UI,跟用什么画图无关。
2. 目标架构 — 三层分离
整个二维图表 widget 必须做成三层分离,这是治本:
┌─────────────────────────────────────────────┐
│ ChartPanel (QWidget) │
│ ┌───────────────────────────────────────┐ │
│ │ 工具条 ToolBar(网格/色阶配置/白化/...) │ │ ← 工具条层(属于面板,不属于绘图区)
│ ├───────────────────────────────────────┤ │
│ │ QwtPlot(数据区 + 固定坐标轴) │ │ ← 绘图层(QwtPlot 管坐标轴与交互)
│ │ • QwtPlotItem: 等值线多边形 / 散点 │ │
│ │ • QwtPlotMagnifier / Panner(交互) │ │
│ ├───────────────────────────────────────┤ │
│ │ ColorBarWidget(独立固定) │ │ ← 图例层(独立 widget,永不参与数据区变换)
│ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
铁律:
- 坐标轴由 QwtPlot 管理,永远固定(不随拖动移动其位置),拖动/缩放时只有数据区内容平移缩放,轴上的刻度数值自动跟随重算。这正是 QwtPlot 的默认行为,也正是原版的行为。
- 色阶条(ColorBar)是独立 widget,放在绘图区下方,绝不放进 QwtPlot 的数据坐标系,因此永远不会随图缩放、不会和横坐标重叠(直接消灭 #1)。
- 工具条属于面板,不属于绘图区(修正 #7)。
3. 框架与依赖
| 用途 | 选用 | 协议 | 说明 |
|---|---|---|---|
| 2D 图表坐标系框架 | QwtPlot(Qwt 6.2+) | QWT License(LGPL + 例外,商用免费) | 坐标轴、缩放、平移、刻度跟随、图例、色阶映射全内置 |
| 等值线多边形算法 | vtkBandedPolyDataContourFilter | BSD(商用免费) | 仅当算法用,不开 VTK 渲染窗口 |
| 等值线(线)算法 | vtkContourFilter / vtkFlyingEdges2D | BSD | 生成等值线及其标注位置 |
| 连续色阶映射 | QwtLinearColorMap | QWT License | 对齐原版 Plotly 的连续插值色阶 |
协议全部商用友好,零授权费。QWT 与 VTK 都不触发开源传染。
VTK 的双重角色(务必理解,否则又会做重):在 3D 视图 / 地图视图里 VTK 是渲染器(开渲染窗口);在本页这种纯二维详情图里,VTK 退化为算法库(只用它的 filter 算多边形,取出顶点交给 QwtPlot 画)。本页不允许出现 QVTKOpenGLWidget / 渲染窗口。
4. 逐项解法(对应 9 个问题)
4.1 色阶条独立固定(治 #1)
- 新建
ColorBarWidget : public QWidget,自绘一条水平色带 + 刻度数值 + 单位。 - 它在布局上位于 QwtPlot 下方,是兄弟 widget,不是 QwtPlot 的内部 item。
- 它的色阶与绘图区用同一个色阶对象(见 §6 色阶服务),保证图与图例配色一致。
- 因为它不在数据坐标系内,永远不随拖动/缩放移动,不会与横坐标轴重叠。
- 刻度数值与原版对齐:用与数据相同的分级断点(原版色阶断点形如 17.105 / 25.937 / ...,等比/对数分布,见 §6)。
4.2 仅数据区缩放、坐标轴固定、轴值跟随(治 #3)
- 用 QwtPlot,坐标轴(
QwtPlot::xBottom/yLeft)由 QwtPlot 框架绘制,位置固定。 - 缩放/平移用 QwtPlot 内置:
QwtPlotMagnifier(滚轮缩放数据区)QwtPlotPanner(拖动平移数据区)- 需要框选缩放可加
QwtPlotZoomer
- 这些组件只改变坐标轴的数值范围(scaleDiv),QwtPlot 自动重算刻度并重绘——轴不动、轴上的数字跟着变,与原版完全一致。
- 原数据 = 四象限坐标系(数据含正负,x/y 轴交叉于原点区域);网格数据 = 单象限(数据全正,原点在左下)。在 QwtPlot 里就是设置不同的轴范围与原点位置,不需要两套框架。
4.3 拖动不再花屏(治 #4)
- 当前"花屏"是手写
QGraphicsView变换 + 重绘时机错误导致。改用 QwtPlot 后,重绘由 QwtPlot 的 replot 机制统一管理,不会出现脏区残留。 - 大数据量等值线多边形若导致拖动卡顿:对
QwtPlotItem开启QwtPlotItem::RenderAntialiased视情况关闭、并启用绘制缓存;必要时对多边形做 LOD 简化(与原版"简化容差"滑块一致,见 §7)。
4.4 纵横比 / 宽度修正(治 #9)
- 原版图是"宽扁"的剖面图(x 范围远大于 y)。当前实现"偏瘦"是因为没有锁定数据纵横比、或 widget 尺寸策略错误。
- 解法:根据数据的 x/y 跨度设定数据区的纵横比;若要求等比例(1 个数据单位 x = 1 个数据单位 y 的像素),自定义布局在 replot 时按数据范围调整 canvas 的宽高比,或固定 y 轴范围、x 轴自适应填充。
- 先对齐原版的"宽扁"观感即可,不必强制物理等比,除非业务要求等比例尺。
4.5 连续插值色阶,对齐 Plotly 配色(治 #2)
- 原版散点/等值线用的是连续插值色阶(Plotly 的 colorscale),当前实现用了离散色带,所以配色不同。
- 用
QwtLinearColorMap,设置与原版相同的控制点颜色 + 位置,开启连续插值(QwtLinearColorMap默认ScaledColors连续模式,不要用FixedColors)。 - 控制点取自原版色阶(紫蓝→蓝→青→绿→黄→橙→红→深红的彩虹序列)。必须从原版抓取实际色值(见 §6 第 4 步),不要凭记忆配色。
- 色阶分布是对数/等比的(断点 17.105, 25.937, 34.382... 比值递增),映射时对数据取 log 再线性映射,否则颜色分布会与原版不同。
4.6 等值线数值标注(治 #5)
- 等值线(contour line)由
vtkContourFilter在各分级阈值生成线,取出线的折点。 - 在 QwtPlot 中用自定义
QwtPlotItem绘制这些线,并在线的合适位置绘制数值标签(label):沿线取中点或曲率较小处,绘制旋转贴合线方向的文本(原版标签是贴着等值线方向旋转的)。 - 受工具条「显示等值线标注」复选框控制开关。
- 「显示等值线提示信息」控制 hover 时的 tooltip(鼠标移到等值线上显示数值),与原版一致。
4.7 页签切换样式归位(治 #6)
- 「原数据 / 网格数据」切换不是两个按钮,而是与「数据集面板的 数据/文件」「右栏的 异常/对象属性」同一种下划线页签样式(之前已专门调过样式,复用那套 QSS/组件,不要重写成 QPushButton)。
- 直接复用既有的下划线 Tab 组件类,保证全客户端切换样式统一。
4.8 工具条归位到网格数据面板内(治 #7)
- 「显示异常 / 显示等值线标注 / 显示等值线提示信息 / 简化容差滑块 / 网格 / 色阶配置 / 白化 / 滤波处理 / 异常标注 / 自动标注 / 另存为」这些属于网格数据视图的工具条,必须在网格数据面板内部的工具条上,不能浮到外层。
- 「原数据」页签的工具条是另一套(网格 / 色阶配置 / 当前图形下拉 / 另存为),各自归属各自页签。
4.9 下方补「描述」页签(治 #8)
- 图下方区域是一个含两个页签的容器:「异常列表」+「描述」,当前只做了异常列表表格,缺「描述」。
- 补上「描述」页签(富文本/纯文本说明区),与原版结构一致。
5. 组件清单与职责(定死,不要改结构)
| 类 | 基类 | 职责 |
|---|---|---|
DatasetDetailPanel |
QWidget | 数据集详情页根容器:顶部下划线页签(原数据/网格数据)+ 内容区 + 下方(异常列表/描述) |
RawDataChartView |
QWidget | 「原数据」散点图:工具条 + QwtPlot(四象限)+ ColorBar |
GridDataChartView |
QWidget | 「网格数据」等值线图:工具条 + QwtPlot(单象限)+ ColorBar |
ContourPlotItem |
QwtPlotItem | 自定义:绘制 VTK 算出的分层填充多边形 + 等值线 + 标注 |
ScatterPlotItem |
QwtPlotItem / QwtPlotSpectroCurve | 散点,按值连续着色 |
ColorBarWidget |
QWidget | 独立色阶条(固定,不入数据坐标系) |
ColorMapService |
(单例/服务) | 全局色阶:断点、颜色、命名保存、对数映射;图与图例同源 |
ContourEngine |
(封装 VTK) | 输入网格数据 → 输出分层多边形 + 等值线折点(封装 vtkBandedPolyDataContourFilter / vtkContourFilter) |
6. 色阶服务(对齐原版的关键)
色阶是"配色一致"的命门,单独抽成服务,图、图例、3D 视图共用同一份(呼应视觉设计规范 §8):
- 断点(分级阈值):采用原版的对数/等比分布。原版断点示例:
17.105, 25.937, 34.382, 35.972, 42.555, 49.051, 56.312, 68.950, 83.029, 98.454, 110.570, 129.660, 158.270, 204.160, 260.820, 321.710, 1335.500(不同数据集断点不同,应由数据的 min/max + 分级规则动态生成,规则与原版一致)。 - 颜色控制点:彩虹序列(深蓝 → 蓝 → 亮蓝 → 青 → 浅绿 → 绿 → 黄绿 → 黄 → 土黄 → 橙 → 黄 → 红 → 深红 → 紫红 → 紫 → 深紫黑)。
- 映射方式:连续插值(
QwtLinearColorMapScaledColors)+ 对数标度(数据先 log,再线性映射到 [0,1])。 - 必须从原版抓真实色值:打开原版页面
http://tenant.geomative.cn/#/projectSpace/datasetMange/datasetInfo?id=...&ddCode=dd_inversion_data,用浏览器开发者工具检查 Plotly 的colorscale配置,或对色阶条截图取色(每个色块取中心像素 RGB)。严禁凭记忆配色 —— 这是 #2 反复出错的根源。 - 命名保存:色阶可命名、保存、切换,调整后图与图例实时刷新(业务规约要求)。
7. 工具条功能对照(两个页签各一套)
原数据页签工具条:网格 | 色阶配置 | 当前图形(下拉:散点图/...)| 另存为
网格数据页签工具条:网格 | 色阶配置 | 白化 | 滤波处理 | ☑显示异常 | ☑显示等值线标注 | ☐显示等值线提示信息 | 简化容差(滑块,0–1)| 异常标注 | 自动标注 | 另存为
- 「简化容差」滑块:控制等值线多边形的简化程度(值越大,多边形越简化、越平滑),对应 VTK 侧对多边形做
vtkDecimatePolylineFilter或道格拉斯-普克简化的容差参数。 - 「白化」「滤波处理」是对网格数据的预处理操作,作用于 ContourEngine 的输入。
- 「自动标注」弹出对话框(见 §8)。
8. 自动标注对话框(复用同一图表组件)
- 「自动标注」弹出的对话框里那张图,必须与主页面网格数据图完全一致——做法是复用同一个
GridDataChartView/ContourPlotItem类的另一个实例,传入同一份数据 + 同一个 ColorMapService,不要另写一套渲染(否则又会出现两张图不一致)。 - 对话框结构:左侧「异常判定规则」(阈值模式 数值/百分位、最小点数、异常类型)+ 右侧统计信息(最大/最小/均值/中位数)+ 图(同主图)+ 自动标注结果列表 + 底部(取消/执行自动标注/确认保存)。
- 因为是普通 QWidget(QwtPlot 而非 VTK 窗口),嵌进对话框无任何 OpenGL 上下文问题,开关对话框不会闪烁/泄漏。
9. 实施顺序(建议分步验收,避免再次大返工)
每一步做完就和原版对照截图验收,通过再进入下一步。不要一次性全做完再看。
- 搭三层骨架:DatasetDetailPanel + 两个页签(下划线样式)+ 下方(异常列表/描述)。先不画图,验证 UI 结构对齐(治 #6/#7/#8)。
- QwtPlot 接入 + 交互:空 QwtPlot,配好固定坐标轴 + Magnifier/Panner,验证"拖动只动数据、轴值跟随、不花屏、纵横比对"(治 #3/#4/#9)。
- ColorBar 独立:加 ColorBarWidget 固定在下方,验证缩放时色阶条不动、不与轴重叠(治 #1)。
- 色阶服务 + 抓原版色值:实现 ColorMapService,从原版抓真实色值与断点,连续对数映射(治 #2 的基础)。
- 散点图:原数据页用 ScatterPlotItem 连续着色,对照原版配色(治 #2)。
- 等值线图:ContourEngine(VTK 算多边形)+ ContourPlotItem 绘制填充 + 等值线 + 标注(治 #2/#5)。
- 工具条联动:显示异常/等值线/提示、简化容差滑块、白化、滤波接通。
- 自动标注对话框:复用图表组件 + 规则面板 + 统计 + 结果。
- 整体对照验收:原数据、网格数据分别与原版并排截图,核对配色/布局/交互/标注/纵横比。
10. 验收标准(写清楚,避免预期错位)
通过标准 = 视觉等价 + 交互一致:
- 配色与原版视觉一致(同一色阶、连续插值、对数分布)
- 坐标轴固定,拖动/缩放只动数据区,轴刻度值跟随
- 色阶条固定在轴下方,不随图缩放,不与坐标轴重叠
- 等值线数值标注显示,贴合线方向
- 原数据=四象限、网格数据=单象限,缩放/移动行为分别与原版一致
- 纵横比/宽扁观感与原版一致
- 拖动流畅、无花屏
- 页签切换为下划线样式(与数据/文件、异常/属性一致)
- 工具条开关位于各自页签面板内
- 下方含「异常列表 + 描述」两个页签
- 自动标注对话框内图与主图完全一致
不作为通过标准(明确排除,避免无意义返工):
- ✗ 与 web 端逐像素 diff 相同(抗锯齿/字体/亚像素天生有别,任何原生方案都无法做到,非缺陷)。
11. 一句话方向
不嵌 Chromium。推翻 QGraphicsView,用 QwtPlot(坐标系/交互/图例)+ VTK 算法(等值线多边形)+ 连续对数色阶(对齐原版),三层分离(绘图区 / 固定坐标轴 / 独立色阶条),UI 三处(页签样式/工具条归位/描述页)按原版恢复。验收按"视觉等价 + 交互一致",非字节级。
本方案针对当前返工,钉死架构与分层,禁止在图表渲染框架上再自由发挥。未覆盖的细节先确认再实现。