[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:bidirectional-tree-line-direction-faq":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 通过两个匹配的图视图解释双向树的箭头方向\n\n## 这个示例构建了什么\n\n这个示例构建了一个全高对比页面，其中包含两个 relation-graph 画布，以及位于它们中间的固定说明列。两个画布都渲染同一棵以根节点为中心的双向树：一组分支向上生长并指向 `R-*` 节点，另一组分支则向下生长并指向 `a` 下方的子节点。\n\n可见差异集中在树的上半部分。左侧图显示的是基础表现形式，右侧图则把上方连线着成红色，并修改其可见箭头，使这些连线看起来朝上。用户可以点击节点和连线，在控制台中查看底层对象，但核心体验是并排进行视觉对比。这个示例的关键点在于：它在不改变树布局所要求的自上而下关系结构的前提下，改变了用户感知到的连线方向。\n\n## 数据是如何组织的\n\n图数据通过 `MyGraph.tsx` 中的 `getMyJsonData(forRightPanel)` 内联创建。它返回一个 `RGJsonData` 对象，其中包含 `rootId: 'a'`、一个 `nodes` 数组和一个 `lines` 数组。在调用 `setJsonData()` 之前，没有外部获取过程，也没有预处理流水线。唯一的转换发生在这个辅助函数内部：当 `forRightPanel` 为 `true` 时，它会在构造上方分支的连线之前设置 `showStartArrow = true`、`showEndArrow = false` 和 `color = '#ff0000'`。\n\n这种结构使对比范围保持得很窄。左右两个面板复用了同一个有根树模型，只有上方分支的连线元数据发生了变化。数据集还在 `R-c` 上设置了 `expandHolderPosition: 'top'`，并在 `c` 上设置了 `expandHolderPosition: 'bottom'`，从而进一步强化了围绕同一个根节点的双向树行为。\n\n在真实项目中，这种数据形态可以表示上游与下游服务、家谱或组织架构中的父子记录、前置任务与后续任务，或者事故分析树中的原因与结果。这里具有可迁移性的关键思想是：图可以保持一个一致的关系模型，同时仅在选定分支上改变渲染出来的箭头语义。\n\n## relation-graph 是如何使用的\n\n`index.tsx` 创建了页面外壳，并将每个图面板分别包裹在各自的 `RGProvider` 中，因此左右两个画布拥有彼此独立的图实例上下文。`MyGraph.tsx` 在每个面板组件内部使用 `RGHooks.useGraphInstance()`，然后执行相同的挂载期初始化顺序：`updateOptions()`、`setJsonData()`、`moveToCenter()` 和 `zoomToFit()`。\n\n共享的 `graphOptions` 对象使用树布局，并设置了 `layoutName: 'tree'` 和 `from: 'top'`，因此整体结构会按垂直方向排列。它还设置了 `treeNodeGapH: 20`、`treeNodeGapV: 100`、矩形节点、`RGLineShape.SimpleOrthogonal`、固定的默认节点尺寸，以及 `RGJunctionPoint.tb`，从而让这种双向树中的垂直连接保持清晰可读。\n\n这个示例没有使用节点插槽、连线插槽、画布插槽、编辑 API 或运行时模式切换。它依赖内置渲染能力，以及数据集里针对每条连线配置的元数据。样式也是最小化且局部的：`my-relation-graph.scss` 将 `.my-graph` 包裹层内的节点文本限定为黑色、`14px` 字号，而页面的大部分框架布局则来自 `index.tsx` 中的内联样式。\n\n## 关键交互\n\n主要交互是对比，而不是操作。页面同时展示两个预先渲染好的图状态，因此查看者可以直接观察某一项连线选项的变化是如何只影响上方分支的。\n\n两个图都绑定了节点点击和连线点击事件，但它们只是把选中的 `RGNode` 或 `RGLine` 对象输出到日志中。它们不会展开分支、编辑图，也不会在不同变体之间切换。两个面板也会在挂载时自动完成初始化，因此无需任何设置控件即可开始对比。\n\n## 关键代码片段\n\n下面这个片段展示了右侧变体是如何在构建数据集之前，通过切换箭头标志和颜色来创建的。\n\n```tsx\nconst getMyJsonData = (forRightPanel = false): RGJsonData => {\n    let showStartArrow, showEndArrow, color;\n    if (forRightPanel) {\n        showStartArrow = true;\n        showEndArrow = false;\n        color = '#ff0000';\n    }\n```\n\n下面这个片段展示了上方分支如何在保持相同 `from` 和 `to` 结构的同时，复用这些特定于变体的连线属性。\n\n```tsx\nlines: [\n    { from: 'R-b', to: 'a', showStartArrow, showEndArrow, color },\n    { from: 'R-c', to: 'a', showStartArrow, showEndArrow, color },\n    { from: 'R-c-1', to: 'R-c', showStartArrow, showEndArrow, color },\n    { from: 'R-c-2', to: 'R-c', showStartArrow, showEndArrow, color },\n    { from: 'R-d', to: 'a', showStartArrow, showEndArrow, color },\n    { from: 'a', to: 'b' },\n```\n\n下面这个片段展示了两个面板共用的树配置。\n\n```tsx\nconst graphOptions: RGOptions = {\n    debug: false,\n    layout: {\n        layoutName: 'tree',\n        from: 'top',\n        treeNodeGapH: 20,\n        treeNodeGapV: 100,\n    },\n    defaultNodeShape: RGNodeShape.rect,\n    defaultLineShape: RGLineShape.SimpleOrthogonal,\n    defaultNodeWidth: 80,\n    defaultNodeHeight: 40,\n    defaultJunctionPoint: RGJunctionPoint.tb\n};\n```\n\n下面这个片段展示了每个面板如何使用图实例 API 加载数据并适配视口，其中右侧面板会选择变体数据集。\n\n```tsx\nuseEffect(() => {\n    const init = async () => {\n        graphInstance.updateOptions(graphOptions);\n        await graphInstance.setJsonData(getMyJsonData(true));\n        graphInstance.moveToCenter();\n        graphInstance.zoomToFit();\n    };\n    init();\n}, []);\n```\n\n下面这个片段展示了页面是如何有意构造成左侧图、中间说明列和右侧图的。\n\n```tsx\n\u003Cdiv className=\"my-graph\" style={{ display: 'flex' }}>\n    \u003Cdiv style={{ height: 'calc(100vh)', width: 'calc((100% - 300px) / 2)' }}>\n        \u003CRGProvider>\n            \u003CMyGraphLeft />\n        \u003C/RGProvider>\n    \u003C/div>\n\n    \u003Cdiv style={{ height: 'calc(100vh)', width: '300px', borderLeft: '#efefef solid 1px', borderRight: '#efefef solid 1px', padding: '15px', fontSize: '14px', lineHeight: '25px', overflowY: 'auto' }}>\n```\n\n## 这个示例的独特之处\n\n这种对比式分析清楚地说明了这个示例的定位：它不是一个通用树图演示，也不是一个宽泛的连线特性目录。它的独特价值在于，通过同时展示两个几乎相同的图实例，来回答一个关于双向树的特定 FAQ。这个示例唯一想传达的内容是：如何通过修改 `showStartArrow` 和 `showEndArrow`，让父侧连线在视觉上看起来是反向的，同时底层自上而下的关系数据依然与布局兼容。\n\n与 `bothway-tree2` 相比，这个示例更聚焦，也更静态。`bothway-tree2` 会在同一个图中探索方向切换和按分支定制，而这个示例则把两种状态硬编码在分离的面板中，因此更容易把“箭头方向”这个知识点单独拎出来说明。与 `advanced-line-usage` 和 `line` 相比，这个示例更强调层级结构：它不是在讲解一组连线形状、连接锚点或标签样式的目录，而是在说明一个只有在以根节点为中心的双向树中才真正有意义的歧义点。\n\n另一个不常见的细节是它周围的页面脚手架。固定的中间说明列、重复使用的 provider、上下分支相反的 expand-holder 位置，以及上方红色连线变体，共同让这个示例更像是一个 FAQ 答案，而不是一个可随意探索的 playground。当需求是把某一条渲染规则讲清楚，而不是暴露大量控件时，这种结构会是更好的起点。\n\n## 这种模式还适用于哪里\n\n这种模式非常适合技术文档、QA 示例和入门材料，在这些场景中，团队需要证明某一项图配置变化所带来的视觉效果。当目标是解释为什么某种渲染结果是正确的时，一组前后对比的双画布往往比运行时开关更清晰。\n\n同样的结构也适用于上游和下游依赖树、父子实体图、血缘视图、审批层级以及故障分析树。在这些场景下，图可以保留一个稳定的数据方向，以满足布局和处理需求，同时通过连线元数据，用用户更符合直觉的方式来呈现分支方向。\n",false,500,1782615384270]