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

16 KiB
Raw Blame History

数据集详情「二维图表」返工实现计划 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}.hppsrc/app/panels/AnomalyTablePanel.*(异常表,列对,复用)。
  • 既有下划线页签组件 buildTabbedPanelmain.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/ 头文件确认签名):QwtPlotQwtPlotItemQwtPlotMagnifierQwtPlotPannerQwtPlotZoomerQwtLinearColorMapQwtScaleEngine/QwtLogScaleEngineQwtTextQwtPlotCanvas


步骤 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 复用 buildTabbedPanelmain.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/yaxisrange/scaleanchor/autorange/side判定原数据到底是「真四象限」还是「单象限+自由平移」,网格数据同样观察。把结论记入本步骤再实现,不靠猜。

Files: RawDataChartView.*GridDataChartView.*(先只含 QwtPlot 空图 + 交互);src/app/CMakeLists.txtqwt

  • Step 2.2 两视图各内嵌一个 QwtPlot。按步骤2.1结论设轴:网格数据 = 单象限x/y 从数据 min原点左下原数据 = 按结论(四象限则轴范围含负、十字交叉;单象限则同网格)。
  • Step 2.3QwtPlotMagnifier(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 : QWidgetpaintEvent 自绘一条水平色带(暂用占位色阶)+ 刻度数值。固定高度(~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 写失败测试(对数映射):
#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.txtapp 段,已 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):分别喂 RawDataChartViewscatter + scatterColorScaleGridDataChartViewgrid + gridColorScale + anomalies异常列表喂 AnomalyTablePanel
  • Step 8.2 main.cpp:删除对旧 DatasetChartView/DatasetDetailPage 的引用;DatasetDetailPanel 改用新 DatasetDetailPagechartReadyopenOrUpdate 链路不变。删除 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功能实现后续单独立项。