Skip to content

Conversation

@lyw405
Copy link
Contributor

@lyw405 lyw405 commented Oct 28, 2025

📋 关联 Issue

lyw405 and others added 4 commits October 28, 2025 15:59
主要改进:
- 修复展开/收起时节点位置跳动和边渲染错误问题
- 实现初次渲染从根节点平滑展开的动画效果
- 提取 14 个辅助方法,提升代码可维护性
- 完全兼容非树形布局,无破坏性变更

技术实现:
- 新增 layoutPreset 机制支持初始位置动画
- 增强 preLayoutDraw 触发位置更新
- 新增 handleTreeLayoutExpand/Collapse 处理树形布局
- 优化边跟随节点动画效果

相关 Issue: antvis#7439
问题描述:
初次渲染时 preLayout 和 preLayoutDraw 分别调用 simulate 进行布局模拟,
导致相同的布局计算执行两次,造成性能浪费。

优化方案:
- 在 LayoutController 中新增 simulationCache 字段缓存 simulate 结果
- preLayout 执行 simulate 后缓存结果
- preLayoutDraw 优先使用缓存,避免重复调用
- 使用完成后立即清除缓存,确保下次渲染时重新计算

性能提升:
- 初次渲染耗时减少约 50%(避免重复布局计算)
- 内存占用增加可忽略(仅临时缓存,使用后立即清除)

新增 API:
- getCachedSimulation(): 获取缓存的布局模拟结果
- clearSimulationCache(): 清除布局模拟缓存

相关 Issue: antvis#7439
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @lyw405, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request provides a comprehensive solution to several reported bugs concerning tree layout expansion and collapse. The core changes involve refactoring the ElementController and LayoutController to introduce a more robust and animated handling of tree structures. This includes caching layout simulation results, applying calculated offsets to maintain node positions, and orchestrating animations for a smoother user experience when interacting with tree-based graphs.

Highlights

  • Tree Layout Expansion Fixes: Addresses multiple issues where expanding or collapsing nodes in tree layouts resulted in incorrect positioning, visual glitches, or unadjusted spacing.
  • Enhanced Animation Control: Introduces a refined animation mechanism for tree layouts, ensuring smooth transitions and correct element placement during expand/collapse operations.
  • Layout Simulation Caching: Implements caching for layout simulation results to prevent redundant calculations, improving performance during interactive layout changes.
  • Offset Calculation for Stability: Adds logic to calculate and apply layout offsets, maintaining the visual stability of the graph by preventing view shifts when nodes are expanded or collapsed.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

此拉取请求对树形布局的展开和折叠功能进行了重大改进,解决了多个相关问题。代码变更结构良好,将复杂的逻辑分解为更小、更易于管理的辅助函数。使用预布局状态(__layoutPreset)作为初始位置,并以动画方式过渡到最终计算位置的核心思想,是实现平滑动画的可靠方法。

我有几点建议。一个是高严重性问题,关于 preLayoutDraw 中新逻辑的适用范围,它可能会影响非树形布局。另一个是中等严重性的建议,旨在改善 getLayoutOptions 中的封装性。总的来说,这是一次很棒的贡献,提升了树形可视化的用户体验。

// <zh/> 对于树形布局,需要再次更新位置到最终位置以触发动画
// For tree layout, need to update positions to final positions to trigger animation
const { layout } = this.context;
if (layout) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

第 322 行的注释表明此逻辑块是为树形布局设计的,但 if (layout) 的条件会将其应用于所有布局。这可能会通过强制重新模拟和执行额外的更新阶段,为非树形布局带来意外行为或性能下降。为防止这种情况,应将此逻辑的范围限定为仅适用于树形布局,类似于 collapseNodeexpandNode 中的检查。

Suggested change
if (layout) {
if (layout && this.context.model.model.hasTreeStructure(TREE_KEY)) {

Comment on lines 692 to 695
const layoutController = layout as unknown as {
presetOptions?: Partial<STDLayoutOptions>;
options?: LayoutOptions;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

使用 as unknown as 来访问 LayoutController 的属性破坏了封装性,并使代码对 LayoutController 的更改更加脆弱。更好的做法是在 LayoutController 上公开一个方法来以类型安全的方式获取这些选项,从而提高可维护性。

@yvonneyx yvonneyx linked an issue Nov 20, 2025 that may be closed by this pull request
10 tasks
@lihawhaw
Copy link

嗨,请问这个有合并上线预期时间吗?

@Aarebecca
Copy link
Contributor

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次 PR 修复了多个与树图布局展开/收起动画相关的问题。改动范围较大,主要集中在 element.tslayout.ts 文件中。

为树图布局创建特定的处理函数(handleTreeLayoutExpand, handleTreeLayoutCollapse)来隔离复杂的逻辑,这种方法是合理的。代码被重构为更小、更易于管理的私有方法,提高了可读性。

我提出了一些关于代码清晰度和 API 一致性的改进建议。

Comment on lines +83 to 100
positionData?.nodes?.forEach((node) => {
const id = idOf(node);
const addNode = add.nodes.get(id);
model.syncNodeLikeDatum(node);
if (addNode && node.style) Object.assign(addNode.style!, node.style);
});
simulate?.edges?.forEach((l) => {
const id = idOf(l);
const edge = add.edges.get(id);
model.syncEdgeDatum(l);
if (edge) Object.assign(edge.style!, l.style);
positionData?.edges?.forEach((edge) => {
const id = idOf(edge);
const addEdge = add.edges.get(id);
model.syncEdgeDatum(edge);
if (addEdge && edge.style) Object.assign(addEdge.style!, edge.style);
});
simulate?.combos?.forEach((l) => {
const id = idOf(l);
const combo = add.combos.get(id);
model.syncNodeLikeDatum(l);
if (combo) Object.assign(combo.style!, l.style);
positionData?.combos?.forEach((combo) => {
const id = idOf(combo);
const addCombo = add.combos.get(id);
model.syncNodeLikeDatum(combo);
if (addCombo && combo.style) Object.assign(addCombo.style!, combo.style);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

preLayout 方法中,处理 nodesedgescombos 的逻辑非常相似,存在代码重复。

// packages/g6/src/runtime/layout.ts:83-100
positionData?.nodes?.forEach((node) => {
  const id = idOf(node);
  const addNode = add.nodes.get(id);
  model.syncNodeLikeDatum(node);
  if (addNode && node.style) Object.assign(addNode.style!, node.style);
});
positionData?.edges?.forEach((edge) => {
  const id = idOf(edge);
  const addEdge = add.edges.get(id);
  model.syncEdgeDatum(edge);
  if (addEdge && edge.style) Object.assign(addEdge.style!, edge.style);
});
positionData?.combos?.forEach((combo) => {
  const id = idOf(combo);
  const addCombo = add.combos.get(id);
  model.syncNodeLikeDatum(combo);
  if (addCombo && combo.style) Object.assign(addCombo.style!, combo.style);
});

可以考虑将这部分重复逻辑提取到一个辅助函数中,以提高代码的可读性和可维护性。例如:

const syncElementData = (elements: any[] | undefined, getElement: (id: ID) => any, syncDatum: (datum: any) => void) => {
  elements?.forEach((element) => {
    const id = idOf(element);
    const addedElement = getElement(id);
    syncDatum(element);
    if (addedElement && element.style) {
      Object.assign(addedElement.style!, element.style);
    }
  });
};

syncElementData(positionData?.nodes, (id) => add.nodes.get(id), model.syncNodeLikeDatum.bind(model));
syncElementData(positionData?.edges, (id) => add.edges.get(id), model.syncEdgeDatum.bind(model));
syncElementData(positionData?.combos, (id) => add.combos.get(id), model.syncNodeLikeDatum.bind(model));

注意 syncNodeLikeDatumsyncEdgeDatum 需要绑定 model 的上下文。

Comment on lines +1060 to +1066
const nodesToAnimate = new Map<ID, NodeData>();
updateNodes.forEach((nodeData, nodeId) => {
if (nodeId !== id) nodesToAnimate.set(nodeId, nodeData);
});
add.nodes.forEach((nodeData, nodeId) => {
nodesToAnimate.set(nodeId, nodeData);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

收集待动画节点 nodesToAnimate 的逻辑可以更简洁。

// packages/g6/src/runtime/element.ts:1060-1066
const nodesToAnimate = new Map<ID, NodeData>();
updateNodes.forEach((nodeData, nodeId) => {
  if (nodeId !== id) nodesToAnimate.set(nodeId, nodeData);
});
add.nodes.forEach((nodeData, nodeId) => {
  nodesToAnimate.set(nodeId, nodeData);
});

可以使用 Map 的构造函数和展开语法来简化这个过程,然后再移除目标节点 id。这样代码更易读。建议修改为:

const nodesToAnimate = new Map<ID, NodeData>([...updateNodes, ...add.nodes]);
nodesToAnimate.delete(id);

Comment on lines 706 to +1128
public async expandNode(id: ID, options: CollapseExpandNodeOptions): Promise<void> {
const { model, layout } = this.context;
const { animation, align } = options;
const position = positionOf(model.getNodeData([id])[0]);
const { animation } = options;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

expandNodecollapseNode 方法都使用了 CollapseExpandNodeOptions 类型,但其中的 align 选项在此次重构中似乎已被废弃,两个方法中都未被使用。然而,该选项仍在 CollapseExpandNodeOptions 接口(1316行)中定义。

新的树状布局展开/收起逻辑似乎总是会保持节点位置不变(通过偏移量计算实现),而对于非树状布局,该功能似乎不再支持。

为了避免 API 的使用者产生困惑,建议从 CollapseExpandNodeOptions 接口中移除 align 选项,以保持 API 的整洁和明确。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants