[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:folder-layout-line-offset":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 运行时连接线偏移控制的文件夹布局\n\n## 这个示例构建了什么\n\n这个示例基于 relation-graph 内置的 `folder` 布局，构建了一个从左到右的文件层级查看器。渲染结果看起来像一棵紧凑的文件夹树：父节点显示为文件夹风格的行，连接线采用低调的正交走线，每个分支的右侧都会出现展开控件。\n\n用户可以通过展开和收起分支来浏览层级结构，还可以在一个浮动控制窗口中于运行时调整一个走线参数。这个示例最值得关注的地方在于，滑块不会重建图数据；相反，它会更新已经渲染出来的连线，让每条连接线都以不同的水平偏移从其源节点引出。\n\n## 数据是如何组织的\n\n源数据一开始是在 `data.ts` 中定义的一棵嵌套树，每个条目都有 `id`、`text` 和可选的 `children`。在任何内容传入 relation-graph 之前，这个示例会先把这棵树转换为显式的 `RGJsonData` 数组结构，也就是 `nodes` 和 `lines`。\n\n这一步预处理很重要，因为示例需要直接控制每一条连接线。树被拍平之后，每个父子关系都会变成一条独立的 line 记录，这样就可以为连线分配 line id、设置显式的连接点，并在之后一次性更新所有在线连接线。在真实应用中，同样的结构也可以表示文件系统、文档大纲、分类层级、菜单树，或者任何适合用文件夹风格展示的嵌套归属结构。\n\n## relation-graph 是如何使用的\n\n图实例通过 `RGProvider` 提供，`MyGraph` 使用 `RGHooks.useGraphInstance()` 来加载数据、让视口居中、适配结果尺寸，并在挂载后更新在线连线。图的配置聚焦于 `folder` 布局：`layoutName: 'folder'`、`from: 'left'`、`treeNodeGapH: 20` 和 `treeNodeGapV: 10` 共同定义了一个紧凑的水平层级结构。\n\n视觉默认值也经过了调整，以强化文件树的阅读体验。节点使用 `100x30` 的矩形，连线使用 `RGLineShape.StandardOrthogonal`，拐角通过 `defaultPolyLineRadius: 4` 变为圆角，默认线条颜色为 `#aaaaaa`，展开控件放在右侧。这个示例还启用了 `reLayoutWhenExpandedOrCollapsed`，因此分支展开或收起后，布局仍会和 `folder` 布局保持对齐，而不会留下过时的间距。\n\n`RGSlotOnNode` 是主要的渲染定制点。这个 slot 会检查 `node.rgChildrenSize`，并为父节点添加一个文件夹图标和浅灰色圆角背景，而叶子节点只保留空的图标位置。这样可以让图保持较窄的视觉宽度，避免把这个示例变成卡片风格的层级展示。\n\n浮动的 `DraggableWindow` 是共享的示例脚手架，但它仍然展示了实用的 relation-graph 集成方式。它的设置面板会从 graph store 读取当前图配置，通过 `setOptions(...)` 更新 `wheelEventAction` 和 `dragEventAction`，并通过 `prepareForImageGeneration()`、`getOptions()` 和 `restoreAfterImageGeneration()` 导出当前画布。\n\n## 关键交互\n\n最主要的交互是浮动窗口中的范围滑块。它控制 `fromOffsetX`，一个相关 effect 会通过 `getLines()` 遍历当前连线，并通过 `updateLine(...)` 重新应用 `fromJunctionPointOffsetX`。这样一来，整张图中的走线变化会立刻可见。\n\n第二个重要交互是分支浏览。由于图使用了 `folder` 布局、把展开控件放在右侧，并启用了展开或收起后的重新布局，用户可以在不破坏层级形状的前提下查看更深层级的内容。\n\n浮动窗口本身也具有交互性：它可以被拖动、最小化、切换到设置面板，并用于导出图像。这些控件来自共享的示例工具，而不是这个示例独有的能力，但它们确实会影响用户探索这个示例的方式。\n\n## 关键代码片段\n\n这个片段表明，示例在加载图之前，会有意把嵌套树数据转换为扁平的 `nodes` 和 `lines`。\n\n```ts\nconst myJsonData: RGJsonData = {\n    rootId: 'a',\n    nodes: [],\n    lines: []\n};\n\nflattenTreeData(rootNodeJson, null, myJsonData.nodes, myJsonData.lines);\nreturn myJsonData;\n```\n\n这个递归辅助函数直接说明了，每一条父子边都会变成一条显式的 line 记录。\n\n```ts\ntreeNodes.forEach((nodeData) => {\n    const node: JsonNode = {\n        id: nodeData.id,\n        text: nodeData.text\n    };\n    nodes.push(node);\n    if (parent) {\n        const line: JsonLine = {\n            from: parent.id,\n            to: nodeData.id\n        };\n        lines.push(line);\n    }\n```\n\n这个配置块说明，示例是围绕 relation-graph 的 `folder` 布局构建的，而不是使用通用的树预设。\n\n```ts\nconst graphOptions: RGOptions = {\n    debug: false,\n    layout: {\n        layoutName: 'folder',\n        from: 'left',\n        treeNodeGapH: 20,\n        treeNodeGapV: 10\n    },\n    defaultNodeShape: RGNodeShape.rect,\n    defaultNodeWidth: 100,\n    defaultNodeHeight: 30,\n```\n\n这个初始化步骤证明，在调用 `setJsonData(...)` 之前，连线走线已经被标准化处理。\n\n```ts\nmyJsonData.lines.forEach((line, index) => {\n    if (!line.id) {\n        line.id = `l${index + 1}`;\n    }\n    line.fromJunctionPoint = RGJunctionPoint.bottom;\n    line.fromJunctionPointOffsetX = fromOffsetX;\n    line.toJunctionPoint = RGJunctionPoint.left;\n});\nawait graphInstance.setJsonData(myJsonData);\n```\n\n这个更新路径是运行时技巧的核心：它修改的是已经渲染好的连接线，而不是重新构建数据集。\n\n```ts\nconst updateMyData = () => {\n    graphInstance.getLines().forEach(line => {\n        graphInstance.updateLine(line, {\n            fromJunctionPointOffsetX: fromOffsetX\n        });\n    });\n};\n```\n\n这个节点 slot 让可展开的父节点看起来像文件夹，而不是普通矩形。\n\n```tsx\n\u003CRGSlotOnNode>\n    {({ node }: RGNodeSlotProps) => {\n        return (\n            \u003Cdiv className={`w-full h-full flex place-items-center gap-2 p-2 ${node.rgChildrenSize > 0 ? 'bg-gray-100 rounded' : ''}`}>\n                {\n                    node.rgChildrenSize > 0\n                        ? \u003CFolderOpen className=\"w-4 h-4 text-blue-700\" />\n                        : \u003Cdiv className=\"w-4 h-4 \">\u003C/div>\n                }\n```\n\n## 这个示例的独特之处\n\n根据对比数据，这个示例并不只是另一个层级查看器。它的独特组合在于：专门的文件夹布局、递归的 tree-to-`RGJsonData` 预处理、加载前的连接点标准化、面向可展开父节点的基于 slot 的文件夹渲染，以及一个在挂载后统一更新所有连接线起始偏移的全图滑块。\n\n这使它与附近的其他示例形成了明确差异。与 `layout-folder2` 相比，这个示例更关注文件结构语义和单一可复用的走线控制，而不是更丰富的卡片式节点展示或间距重排控制。与 `tree-data` 相比，它更进一步，把源树拍平成显式连线，这正是它能够在加载前配置走线并在之后批量更新在线连接线的原因。与 `io-tree-layout` 或 `layout-tree` 相比，重点并不在切换预设或方向，而是在保持一个稳定的文件夹查看器配置的同时，调整所有连接线从源节点引出的方式。\n\n对比数据也提醒不要夸大它的独特性。它并不是唯一使用 `folder` 布局的示例，浮动工具窗口、画布设置和图像导出也都来自共享的示例脚手架。这里真正突出的，是在一个偏查看器场景的示例中，将文件夹式展示和连接线偏移控制聚焦地组合在了一起。\n\n## 这种模式还适用于哪里\n\n这种模式非常适合那些需要可读的嵌套结构、但又不需要完整图编辑器的产品场景。典型候选包括云存储浏览器、权限继承树、商品分类树、文档或页面大纲、构建产物文件夹，以及代码归属层级。\n\n当后端数据以嵌套对象形式到达，而 UI 又需要在渲染后进行连线级别的走线控制时，这种预处理模式同样可以复用。把树拍平成显式的节点和连线，会为之后的连接线几何控制、高亮或条件化连线更新提供一个清晰的扩展点，同时不需要改变原始领域数据的形态。\n",false,500,1782615388055]