geopro/docs/superpowers/plans/2026-06-11-dataset-detail-c...

206 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 数据集详情「二维图表」返工实现计划 v2QwtPlot
> **For agentic workers:** REQUIRED SUB-SKILL: 用 superpowers:subagent-driven-development 或 executing-plans 执行。步骤用 `- [ ]`。
> **铁律(来自返工方案):** 每一步做完**必须**与原版对照验收,通过再进下一步——不许一次性做完才看。任何对原版的不确定,**必须用 Playwright 实地学习原版**,禁止联想猜测。
**Goal:** 推翻当前 QGraphicsView 二维图表按《Geopro3.0_二维图表返工技术方案.md》用 **QwtPlot坐标系/交互/图例三层分离)+ VTK 算法(等值线多边形)+ 连续对数色阶** 重做「原数据散点图」与「网格数据等值线图」,修复用户列出的 9 个问题,达到**视觉等价 + 交互一致**(非字节级像素)。
**Architecture:** 三层分离——工具条(属于面板)/ QwtPlot数据区 + 固定坐标轴 + Magnifier/Panner 交互)/ 独立 ColorBarWidget不入数据坐标系。等值线多边形复用已有 `ContourBands`(VTK 算法);色阶用 `QwtLinearColorMap` 连续插值 + 对数标度图与图例同源ColorMapService
**Tech Stack:** C++17、Qt6 Widgets、**Qwt 6.2(已集成,见 cmake/qwt.cmake目标名 `qwt`,头在 external/qwt-src/src**、VTK 9.6仅算法、GoogleTest/CTest。
**依据:** `docs/Geopro3.0_二维图表返工技术方案.md`(架构权威)、`docs/superpowers/specs/2026-06-11-dataset-detail-view-design.md`(数据/接口)。
---
## 0. 复用与替换盘点(不要重造已有的)
**复用(不动):**
- `src/data/api/ApiDatasetRepository.*`、`src/data/dto/DatasetChartDto.*`、`src/controller/DatasetDetailController.*`(数据层+编排,`ChartData` 已带 scatter/grid/colorScale×2/anomalies
- `src/render/ContourBands.*`VTK 算分层多边形+等值线几何,作为等值线图的几何来源 = 方案的 ContourEngine
- `src/core/model/{Field,ColorScale,Anomaly}.hpp`、`src/app/panels/AnomalyTablePanel.*`(异常表,列对,复用)。
- 既有下划线页签组件 `buildTabbedPanel`main.cpp 中「异常/对象属性」面板用的同款§步骤1 复用)。
**替换(删除/重写):**
-`src/app/panels/chart/DatasetChartView.*`(裸 QGraphicsView—— 删除。
-`src/app/panels/DatasetDetailPage.*`(旧两按钮+图表)—— 重写为基于 QwtPlot 的视图。
- `src/app/panels/DatasetDetailPanel.*`、`main.cpp` 接线 —— 按新组件调整。
---
## 文件结构(新建/修改)
| 文件 | 职责 |
|---|---|
| `src/app/panels/chart/ColorMapService.hpp/.cpp`(建) | 色阶服务:断点+颜色、连续对数映射、产出 `QwtLinearColorMap` 与离散色带;图/图例同源 |
| `src/app/panels/chart/ColorBarWidget.hpp/.cpp`(建) | 独立色阶条 QWidget固定不入数据坐标系 |
| `src/app/panels/chart/ScatterPlotItem.hpp/.cpp`(建) | QwtPlotItem散点方块按连续色阶着色 |
| `src/app/panels/chart/ContourPlotItem.hpp/.cpp`(建) | QwtPlotItem画 ContourBands 分层填充多边形 + 等值线 + 标注 + 异常叠加 |
| `src/app/panels/chart/RawDataChartView.hpp/.cpp`(建) | 原数据页:工具条 + QwtPlot + ScatterPlotItem + ColorBarWidget |
| `src/app/panels/chart/GridDataChartView.hpp/.cpp`(建) | 网格数据页:工具条 + QwtPlot + ContourPlotItem + ColorBarWidget |
| `src/app/panels/DatasetDetailPage.hpp/.cpp`(重写) | 单 ds 页:下划线页签(原数据/网格数据) + 两视图 + 下方(异常列表/描述) |
| `src/app/panels/DatasetDetailPanel.hpp/.cpp`(改) | 多 Tab 壳(保留 dsId 去重/反向联动) |
| `src/app/panels/DescriptionPanel.hpp/.cpp`(建) | 下方「描述」页(纯文本/只读富文本占位) |
| `src/app/panels/chart/DatasetChartView.*`(删) | 旧 QGraphicsView 渲染器 |
| `src/app/CMakeLists.txt`(改) | 注册新文件 + 链接 `qwt`;移除 DatasetChartView |
| 测试 | ColorMapService 对数映射 / ContourBands 既有 / 散点着色 单测 |
**构建/测试命令(本机工具链):**
- 构建:`powershell.exe -ExecutionPolicy Bypass -File scripts/dev-build.ps1`
- 测试:`powershell.exe -ExecutionPolicy Bypass -File scripts/dev-test.ps1`
- 运行(视觉验收):`build/release/src/app/geopro_desktop.exe`(需登录)
**Qwt 关键类参考**(实现时可查 `external/qwt-src/src/` 头文件确认签名):`QwtPlot`、`QwtPlotItem`、`QwtPlotMagnifier`、`QwtPlotPanner`、`QwtPlotZoomer`、`QwtLinearColorMap`、`QwtScaleEngine`/`QwtLogScaleEngine`、`QwtText`、`QwtPlotCanvas`。
---
## 步骤 1三层骨架 + UI 结构(治 #6 #7 #8
**目标**:先把布局结构做对,不画图。下划线页签、工具条归位、下方双页签。
**研究原版Playwright**:对照 `assets/web-datasetinfo-*.png` 与实测——确认 (a) 原数据/网格数据 = 下划线页签(非按钮);(b) 两个页签各自的工具条项§方案7(c) 下方 = 异常列表 + 描述 双页签。
**Files:** 重写 `DatasetDetailPage.*`;建 `DescriptionPanel.*`;改 `DatasetDetailPanel.*`、`src/app/CMakeLists.txt`。
- [ ] **Step 1.1** 先研究:用 Playwright 打开 `datasetInfo?id=1439812897218560&ddCode=dd_inversion_data`,截图原数据/网格两页签的工具条与下方区域,确认结构(不靠记忆)。
- [ ] **Step 1.2** 复用 `buildTabbedPanel`main.cpp 中「异常/对象属性」用的下划线页签)做 `DatasetDetailPage` 顶部「原数据/网格数据」切换;内容区先放两个空 `QWidget` 占位RawDataChartView/GridDataChartView 后续填)。
- [ ] **Step 1.3** 下方用同款 `buildTabbedPanel` 做「异常列表(现有 `AnomalyTablePanel`/ 描述(新 `DescriptionPanel`,先放只读文本占位)」。
- [ ] **Step 1.4** 工具条:原数据页工具条 = [网格][色阶配置][当前图形▼][另存为];网格数据页工具条 = [网格][色阶配置][白化][滤波处理][☑显示异常][☑显示等值线标注][☐显示等值线提示信息][简化容差滑块][异常标注][自动标注][另存为]。**先只放按钮/控件占位(无功能)**,位置在各自页签视图内部顶部。
- [ ] **Step 1.5** 构建通过;启动 app 双击 ds**对照原版验收**:页签是下划线样式、工具条在各自页签内、下方有两个页签。截图发用户确认后再进步骤 2。
---
## 步骤 2QwtPlot 接入 + 交互(治 #3 #4 #9
**研究原版Playwright关键——消除「四象限」歧义**
- [ ] **Step 2.1** 在原版散点页拖动/缩放,观察:坐标轴是否固定、刻度值是否跟随、能否平移到数据范围外(负区)。抓 Plotly `layout.xaxis/yaxis`range/scaleanchor/autorange/side。**判定原数据到底是「真四象限」还是「单象限+自由平移」**,网格数据同样观察。把结论记入本步骤再实现,不靠猜。
**Files:** `RawDataChartView.*`、`GridDataChartView.*`(先只含 QwtPlot 空图 + 交互);`src/app/CMakeLists.txt` 链 `qwt`
- [ ] **Step 2.2** 两视图各内嵌一个 `QwtPlot`。按步骤2.1结论设轴:网格数据 = 单象限x/y 从数据 min原点左下原数据 = 按结论(四象限则轴范围含负、十字交叉;单象限则同网格)。
- [ ] **Step 2.3**`QwtPlotMagnifier(plot->canvas())`(滚轮缩放)+ `QwtPlotPanner(plot->canvas())`(拖动平移)。确认:**轴位置不动、只有数据区平移缩放、刻度数值自动跟随**QwtPlot 默认行为)。
- [ ] **Step 2.4** 纵横比(#9原版剖面"宽扁"。先用 `plot->setAxisScale` 固定 y 数据范围、x 自适应填充宽度;若需等比例尺,在 resize/replot 时按数据 x/y 跨度调 canvas 宽高(方案 §4.4)。
- [ ] **Step 2.5** 构建+运行,**对照原版验收**:拖动只动数据、不花屏、轴值跟随、宽扁比例接近。截图确认后进步骤 3。
---
## 步骤 3独立色阶条 ColorBarWidget治 #1
**Files:** `ColorBarWidget.*`;放进 Raw/GridDataChartView 布局中 QwtPlot **下方**(兄弟 widget
- [ ] **Step 3.1** `ColorBarWidget : QWidget``paintEvent` 自绘一条水平色带(暂用占位色阶)+ 刻度数值。固定高度(~36px色条+文字)。
- [ ] **Step 3.2** 布局:`QVBoxLayout`= [工具条][QwtPlot stretch][ColorBarWidget 固定高]。**ColorBar 不入 QwtPlot 坐标系**。
- [ ] **Step 3.3** 运行验收:缩放/拖动主图时色阶条**不动、不与横轴重叠**(治 #1)。截图确认进步骤 4。
---
## 步骤 4ColorMapService + 抓原版真实色值(治 #2 基础)
**研究原版Playwright关键**
- [ ] **Step 4.1** 抓原版色值与映射方式(**严禁凭记忆配色**
- 散点页:抓 Plotly `data[0].marker.colorscale`[[pos,color],…])、`cmin/cmax`、是否连续;判断标度(线性 vs 对数)——对色阶条断点 `17.105,25.937,…` 做比值检验确认对数/等比。
- 网格页:色阶来自 `colorGradation/getDetail`(已知 `properties.colorBar=[[值,rgba],…]`、type1 原数据/type2 网格)。
- 取色兜底:对色阶条截图每格中心取 RGB。
**Files:** `ColorMapService.hpp/.cpp`;测试 `tests/app/test_colormap_service.cpp`
- [ ] **Step 4.2 写失败测试**(对数映射):
```cpp
#include <gtest/gtest.h>
#include "panels/chart/ColorMapService.hpp"
using geopro::app::ColorMapService;
TEST(ColorMapService, LogMapsValueToUnitInterval) {
ColorMapService s;
s.setRange(10.0, 1000.0, /*logScale=*/true);
EXPECT_NEAR(s.normalized(10.0), 0.0, 1e-9);
EXPECT_NEAR(s.normalized(1000.0), 1.0, 1e-9);
EXPECT_NEAR(s.normalized(100.0), 0.5, 1e-9); // log10 中点
}
```
- [ ] **Step 4.3** 注册测试到 `tests/CMakeLists.txt`app 段,已 `target_include_directories ... src/app`);跑确认失败(编译失败)。
- [ ] **Step 4.4 实现** `ColorMapService`
-`core::ColorScale`(离散断点+颜色,来自 colorBar+ `vmin/vmax/logScale`
- `double normalized(double v)`log 时 `(log(v)-log(vmin))/(log(vmax)-log(vmin))`否则线性clamp [0,1]。
- `QwtLinearColorMap* makeQwtColorMap()`:用断点的归一化位置 + 颜色 `addColorStop`,连续插值(默认 ScaledColors
- `core::Rgba colorAt(double v)`:连续插值取色(散点用);以及离散 `bandColor`(等值线用)。
- 图与图例ColorBarWidget+ 等值线图都用**同一个 ColorMapService 实例**。
- [ ] **Step 4.5** 跑测试通过;提交。
---
## 步骤 5原数据散点图治 #2
**研究原版Playwright**:确认散点 = 方块 symbol、白描边、按 vlist 连续着色、x:y 关系步骤2结论
**Files:** `ScatterPlotItem.*`;接入 `RawDataChartView`
- [ ] **Step 5.1** `ScatterPlotItem : public QwtPlotItem`,重写 `draw(QPainter*, xMap, yMap, canvasRect)`:对每个点 `(xlist[i],ylist[i])``xMap.transform/yMap.transform` 映射到像素,画固定像素边长的方块(白描边 1px填色 = `ColorMapService::colorAt(vlist[i])`。`rtti()` 返回自定义。
- [ ] **Step 5.2** `RawDataChartView::setData(scatter, colorMapService)`:建 ScatterPlotItem attach 到 QwtPlot设轴范围为数据包围盒`replot()`ColorBarWidget 用同一 service 刷新。
- [ ] **Step 5.3** 运行,**与原版散点并排对照配色**#2颜色应连续插值、与原版一致。截图确认进步骤 6。
---
## 步骤 6网格数据等值线图治 #2 #5
**研究原版Playwright**:确认填充色带(离散 banded+ 黑色等值线 + **线上数值标注**(贴线方向)+ 数据凸包白边。
**Files:** `ContourPlotItem.*`(复用 `ContourBands`);接入 `GridDataChartView`
- [ ] **Step 6.1** `ContourPlotItem : QwtPlotItem`,持 `ContourBandsResult`(来自 `geopro::render::buildContourBands(grid, colorScale, opt)`)。`draw()`
- 填充:每个 `BandPolygon` 用 xMap/yMap 把局部坐标映射到像素,`painter->drawPolygon`,填 `band.color`(离散)。
- 等值线:每条 `ContourLine` `drawPolyline`(黑、细);受「显示等值线标注」控制在线中段绘制旋转贴线的数值文本(#5
- [ ] **Step 6.2** 异常叠加:把 ds 异常(`core::Anomaly`,局部坐标)按 markType 画在同一 item 之上(虚线多段线/多边形/点),受「显示异常」控制。
- [ ] **Step 6.3** `GridDataChartView::setData(grid, gridColorMapService, anomalies)`:构建 ContourPlotItem attach轴=数据范围ColorBar 用网格色阶type2
- [ ] **Step 6.4** 运行,**与原版网格图并排对照**#2 配色、#5 标注、凸包白边、宽扁比例)。截图确认进步骤 7。
---
## 步骤 7工具条联动显示开关 + 简化容差)
- [ ] **Step 7.1** 「显示异常 / 显示等值线标注 / 显示等值线提示信息」复选框 → 切 ContourPlotItem 对应开关 + replot。
- [ ] **Step 7.2** 「简化容差」滑块01→ 改 `ContourOptions::simplifyTol` 重算 ContourBands + replot对应原版滑块
- [ ] **Step 7.3** 「白化 / 滤波处理 / 网格 / 色阶配置 / 异常标注 / 自动标注 / 另存为」**先做占位按钮**(弹「待实现」或留空槽),功能后续单独立项。运行验收开关行为与原版一致。
---
## 步骤 8接线 DatasetDetailController + main.cpp替换旧渲染
- [ ] **Step 8.1** `DatasetDetailPage::setData(ChartData)`:分别喂 `RawDataChartView`scatter + scatterColorScale`GridDataChartView`grid + gridColorScale + anomalies异常列表喂 `AnomalyTablePanel`
- [ ] **Step 8.2** `main.cpp`:删除对旧 `DatasetChartView`/`DatasetDetailPage` 的引用;`DatasetDetailPanel` 改用新 `DatasetDetailPage`。`chartReady`→`openOrUpdate` 链路不变。删除 `src/app/panels/chart/DatasetChartView.*` 及其 CMake 注册。
- [ ] **Step 8.3** 全量构建 + `dev-test.ps1` 不回归;启动 app 双击 ds **整体对照验收**
---
## 步骤 9整体对照验收治全部 9 项)
- [ ] **Step 9.1** 原数据、网格数据分别与原版并排截图,逐条核对方案 §10 验收清单:配色/轴固定/色阶条固定/标注/坐标系象限/宽扁比例/拖动流畅/页签样式/工具条归位/下方双页签。
- [ ] **Step 9.2** cpp-reviewer 审查本次改动;处理 CRITICAL/HIGH。
- [ ] **Step 9.3** 收尾提交。
---
## 验收标准(方案 §10视觉等价 + 交互一致,非字节级)
- [ ] 配色与原版视觉一致(连续插值、对数分布、同色阶)
- [ ] 轴固定、拖动/缩放只动数据区、刻度值跟随、不花屏
- [ ] 色阶条固定轴下方、不随图缩放、不与轴重叠
- [ ] 等值线数值标注显示、贴线方向
- [ ] 原数据/网格数据坐标系象限与缩放行为分别与原版一致
- [ ] 宽扁比例与原版一致
- [ ] 页签下划线样式、工具条各自页签内、下方异常列表+描述双页签
- [ ] 全量测试不回归
- ✗(明确不作为标准):与 web 逐像素 diff 相同
---
## 自审Spec/方案 覆盖核对)
- 9 个问题逐条 → 步骤 1(#6/#7/#8)、2(#3/#4/#9)、3(#1)、4-5(#2)、6(#2/#5) 全覆盖。✓
- 复用项ContourBands/数据层/AnomalyTablePanel/buildTabbedPanel已标注不重造。✓
- 替换项DatasetChartView/旧 DatasetDetailPage明确删除。✓
- 每步含「Playwright 研究原版 + 对照验收」检查点(铁律)。✓
- 可测部分ColorMapService 对数映射)有 TDD视觉部分以对照验收为准方案明确视觉等价非像素级。✓
- 待澄清/研究项(步骤 2.1 四象限歧义、步骤 4.1 真实色值与标度)已显式列为开工先研究项,不靠猜。✓
> 工具功能(白化/滤波/自动标注/网格化/色阶配置/另存为/导出)本计划只做**占位归位**(治 #7功能实现后续单独立项。