9.4 KiB
全局 Sigma 图谱平滑缩放与左下缩放按钮设计
日期:2026-06-26
分支:codex/fix-global-graph-zoom-controls
关联:GitHub issue #73
背景
当前工作台图谱有两套视图:
- 全局视图使用 Sigma/Graphology。
- 社区视图使用现有 DOM 图谱相机。
用户反馈集中在全局视图:滚轮和触控板缩放太快,轻轻一滑就突然变大或变小;继续缩小还可能让节点小到几乎消失。社区视图的缩放手感相对舒服,原因是它按真实滚轮距离连续计算缩放,并且有缩放边界。
这次 issue #73 提供的是重要线索,不作为唯一方案来源。结合当前代码后,推荐方案是:工作台全局 Sigma 图谱继续支持滚轮/触控板缩放,但不再使用 Sigma 默认的“按事件方向跳档”的滚轮手感;改为项目自己的连续缩放逻辑,并新增左下独立的放大/缩小按钮。
目标
- 全局图谱滚轮/触控板缩放变得平顺、可控,接近社区视图现有手感。
- 全局图谱不再因为轻微滑动突然变很大或很小。
- 全局图谱缩放有合理上下限,避免无限放大或无限缩小。
- 全局图谱左下增加独立的
+/-缩放按钮。 - 滚轮、触控板、按钮使用同一套缩放规则,不出现三种不同手感。
不做
- 不改离线 HTML 的完整体验;本次只保证共享代码改动不主动破坏离线入口。
- 不改社区视图的缩放体验。
- 不改图谱数据、选择、搜索、筛选、Pin、社区高亮、右抽屉等功能语义。
- 不引入新的图谱库或新的 npm 依赖。
- 不修改
node_modules/。
用户体验定义
1. 滚轮和触控板仍然可以缩放
全局图谱保留滚轮/触控板缩放。改变的是缩放计算方式,不是取消滚轮缩放。
2. 缩放必须连续
触控板轻滑一点,只缩放一点;滑动距离更大,缩放幅度才更大。
当前 Sigma 默认滚轮逻辑主要看滚轮方向,一次事件就是固定倍率跳档。这个模型会让高频小 delta 的触控板变成连续猛跳。新设计要改为按真实 deltaY 计算缩放倍率。
参考社区视图现有逻辑:
- 先把 wheel delta 标准化为像素距离。
- 再用指数曲线把 delta 映射成缩放倍率。
- 单次缩放倍率做防甩动限制。
全局 Sigma 视图采用同一类规则:
nextRatio = currentRatio * exp(normalizedDeltaY * WHEEL_ZOOM_SPEED)
说明:
- 社区视图的
scale越大表示越放大。 - Sigma 的
ratio越小表示越放大。 - 所以全局 Sigma 用
exp(+deltaY * speed),与社区视图的scale * exp(-deltaY * speed)在手感方向上对应。 WHEEL_ZOOM_SPEED使用社区视图当前舒服的值:0.0016。只有浏览器实测证明全局 Sigma 与社区视图仍有明显偏差时,才允许带测试记录调整这个常量;不能回到按事件跳档。
3. 缩放必须稳定
滚轮/触控板缩放时,以鼠标所在点为缩放锚点。用户指着哪里缩放,哪里就尽量留在原地。
按钮缩放时,以当前画面中心为缩放锚点。点击 + 或 - 不应该让画面突然飞到别处。
4. 缩放必须有边界
全局 Sigma 相机要设置 minCameraRatio 和 maxCameraRatio,并且项目自己的缩放逻辑也要遵守同一边界。
边界目标:
- 最大放大:能看清局部节点,但不把节点放到失控巨大。
- 最大缩小:能退回全局关系,但不让节点小到消失。
实现初始值:
- 放大下限:
minCameraRatio = 0.3。 - 缩小上限:
maxCameraRatio = 3。
如果视觉验证发现这两个值仍然让节点过大或过小,必须用新的浏览器验证结果说明为什么调整,并同步更新测试预期。无验证记录时,不继续调参。
5. 滚轮缩放不能积压动画
滚轮/触控板缩放应该即时响应,不排队播放动画。用户停止滚动后,画面也应很快停下,不能继续追一串旧动画。
因此:
- 滚轮/触控板缩放直接更新相机状态。
- 按钮缩放可以有短动画,但下一次点击必须接管上一次动画目标。
6. 按钮步长要明确但不猛
左下缩放按钮是地图导航控件,不是精细触控板替代品。
按钮点击一次使用中等固定步长:
+:放大一档,ratio乘以1 / 1.18。-:缩小一档,ratio乘以1.18。
这个步长比触控板轻滑更明确,但不能像当前全局滚轮那样猛跳。
交互布局
新增一个独立的左下缩放控件:
- 位置:全局图谱画布左下角。
- 内容:竖向排列
+和-。 - 样式:沿用现有图谱工具条的半透明纸面风格,但作为单独控件存在。
- 与现有工具条关系:不放进顶部“筛选 / 图例 / 回全图”工具条。
- 与“回全图”关系:
+/-只缩放;“回全图”继续负责回到全局构图。
控件区域本身要阻止画布滚轮缩放和点击穿透,避免用户在按钮附近滚动时误操作图谱。
架构设计
1. 缩放控制归属
缩放控制放在共享图谱引擎的 Sigma 全局渲染路径中,而不是放在工作台 React 外壳里。
理由:
- 当前全局图谱相机由
packages/graph-engine/src/render/sigma-global-renderer.ts管理。 - Sigma 的鼠标事件、相机状态、锚点缩放都在这一层更容易正确处理。
- 工作台外壳不应该理解 Sigma 相机细节。
2. Sigma 默认滚轮处理
Sigma 的 mouse captor 会先发出 wheel 事件,再执行默认缩放。项目应监听这个 wheel 事件并调用 preventSigmaDefault(),阻止 Sigma 默认跳档缩放,然后执行项目自己的连续缩放。
需要使用的 Sigma 能力:
getMouseCaptor().on("wheel", handler):接管滚轮事件。- wheel payload 的
original.deltaY/original.deltaMode:读取真实滚轮输入。 getCamera().getState():读取当前相机。getViewportZoomedState(pointer, nextRatio):按鼠标位置计算新相机状态。getCamera().setState(nextState):即时更新滚轮缩放。
如果某个运行环境缺少上述能力,必须降级为保守的 Sigma 参数方案,而不是让全局缩放不可用。
3. 缩放按钮
createGraphToolbar 不承载缩放按钮。新增一个独立的 Sigma 缩放控件创建函数,挂载到 Sigma 全局 route shell 内。
控件通过 renderer 暴露的缩放方法驱动相机,例如:
zoomIn()zoomOut()
按钮缩放使用当前 Sigma 容器中心点作为锚点,计算方式与滚轮一致,只是输入为固定倍率。
4. 与现有高亮相机动画的关系
当前 main 已经有全局社区高亮与轻微相机构图动画。缩放改动必须与它共存:
- 选中社区后仍允许现有轻微构图。
- 用户滚轮/按钮缩放后,以用户缩放后的相机为准。
- 滚轮缩放不触发社区选择或清空选择。
- “回全图”仍然可以回到全局构图。
错误与降级
- 如果无法读取真实 wheel delta,使用 Sigma wheel payload 的 delta 做保守计算,但仍要避免默认
1.7跳档。 - 如果无法使用
getViewportZoomedState,按钮和滚轮都不应让画面飞走;可以退化为以当前相机中心缩放。 - 如果 Sigma runtime 不支持 mouse captor,则至少设置较温和的
zoomingRatio与相机上下限,保证不出现无限缩放。 - 所有异常通过现有
onFatalError路径上报,不让整张图谱黑屏。
测试计划
单元测试
-
sigma-global-renderer.test.ts- 创建 Sigma 时包含合理的相机边界。
- wheel handler 会阻止 Sigma 默认缩放。
- 小
deltaY只产生小幅缩放,大deltaY产生更明显缩放。 - 连续 wheel 不排队动画,使用即时相机更新。
- wheel 缩放使用鼠标位置作为锚点。
zoomIn/zoomOut使用同一套缩放计算与相机边界。
-
renderer-boundary.test.ts- 左下缩放控件存在。
+/-点击分别触发放大/缩小回调。- 控件点击不穿透到图谱选择。
-
gestures.test.ts或等价覆盖- 滚轮经过缩放控件时不触发图谱缩放。
浏览器验证
- 启动工作台,进入全局图谱。
- 使用触控板轻滑:图谱只轻微缩放,不猛跳。
- 连续滚动后松手:画面快速停止,不继续追动画。
- 鼠标停在节点附近滚轮缩放:该区域保持稳定,不飞跑。
- 多次缩小:节点仍可见,不能缩到消失。
- 多次放大:节点不会失控巨大。
- 点击左下
+/-:画面按中等步长缩放,且以画面中心为基准。 - 选中社区后再缩放:高亮仍在,缩放正常。
- 回全图仍按原语义工作。
回归检查
npm run test -w @llm-wiki/graph-engine- 受影响情况下运行 Sigma 全局浏览器验证脚本。
- 若改动 CSS 或控件布局,打开工作台实际查看浅色/深色下左下按钮是否清晰、不遮挡主要图谱内容。
完成标准
这个任务实现完成时,必须同时满足:
- 工作台全局图谱滚轮/触控板缩放手感接近社区视图,不再轻滑猛跳。
- 工作台全局图谱缩放有上下限,不会无限缩小或无限放大。
- 工作台全局图谱左下有独立
+/-缩放按钮。 - 按钮和滚轮使用同一套缩放规则。
- 现有全局社区高亮、搜索、筛选、Pin、回全图没有被破坏。
- 单元测试和浏览器验证通过。