[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:node-descendant-subtree-highlight":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 突出显示某个节点的后代子树\n\n## 本示例构建了什么\n\n本示例构建了一个占满整个视口的层级查看器，它从一棵预加载、以字母标记节点的树开始，让用户一次查看其中一个分支。图以居中的网络形式展示，节点为蓝色圆形，连线同样为蓝色；同时还有一个浮动辅助窗口，用于说明交互方式并提供共享的演示工具。\n\n核心交互是对子树进行聚焦。当用户点击一个非叶子节点时，该节点及其所有后代会保持完全可见并变为紫色，而无关节点则会淡出到背景中。点击画布会清除这一临时聚焦状态，并恢复完整图形。\n\n最重要的一点是，每次点击后都不会重建图数据。该示例会在内存中保留一份已加载的层级数据，并通过 relation-graph 实例 API 原地修改节点和连线的状态。\n\n## 数据如何组织\n\n数据集直接在 `initializeGraph` 内声明为一个 `RGJsonData` 对象。它采用扁平结构，包含 `rootId`、由 `{ id, text }` 记录组成的 `nodes` 数组，以及由 `{ id, from, to }` 记录组成的 `lines` 数组。只要调用 `setJsonData()`，relation-graph 就能根据这一结构重建出层级关系。\n\n在 `setJsonData()` 之前没有任何预处理步骤。唯一的派生逻辑发生在图已经加载之后：节点点击时，代码会沿着运行时节点对象的 `node.lot.childs` 链收集后代 id。这意味着该示例展示的是一种运行时检查模式，而不是数据转换流水线。\n\n在真实应用中，相同的结构可以表示分类树、组织单元、文件夹层级、产品装配关系或权限继承关系。这里的字母标签只是这种交互模式的占位数据。\n\n## relation-graph 的使用方式\n\n该演示被包裹在 `RGProvider` 中，因此在 `MyGraph` 内可以通过 hooks 访问图实例。图本身使用 `center` 布局、圆形节点以及固定的 60 x 60 节点尺寸，并将蓝色作为默认节点和连线颜色。内联数据加载完成后，实例会将图重新居中并缩放到适配整个视图。\n\n本示例依赖 `RGHooks.useGraphInstance()` 而不是 refs。这个 hook 用在两个位置：主图组件用它来调用 `setJsonData()`、`moveToCenter()`、`zoomToFit()`、`getNodes()`、`getLines()`、`updateNode()`、`updateLine()` 和 `dataUpdated()`；共享的 `DraggableWindow` 设置面板则使用同一图上下文来修改滚轮与拖拽行为，并导出图片。\n\n当前源码中没有渲染任何 relation-graph slot，也不存在编辑工作流。这是一个只读查看器，用来演示如何在图加载后通过实例 API 修改图状态。导入的 SCSS 文件只是一个带空规则块的脚手架，因此可见样式主要来自图选项和运行时状态覆盖，而不是 CSS 皮肤。\n\n## 关键交互\n\n主要交互是点击节点。点击一个非叶子节点时，代码会递归收集被点击节点及其所有后代，然后应用一种“聚焦加上下文”的状态：选中分支内的节点保持不透明并变为紫色，无关节点淡出，只有所选子树内部的连线会被重新着色。\n\n第二个重要交互是点击画布。画布处理器不会重新加载数据，而是清除临时的节点和连线覆盖状态，并通过 `dataUpdated()` 提交这次重置。\n\n浮动辅助窗口提供了次级工具交互。它可以被拖动、最小化、切换到设置面板、通过 `setOptions()` 修改滚轮与拖拽行为，并通过 `prepareForImageGeneration()` 和 `restoreAfterImageGeneration()` 将图导出为图片。这些控制项很实用，但它们来自共享的演示脚手架，而不是子树高亮本身的专属实现。\n\n## 关键代码片段\n\n下面的片段展示了在应用任何点击逻辑之前，该演示如何被配置为一个居中、圆形、固定尺寸的层级图。\n\n```tsx\nconst graphInstance = RGHooks.useGraphInstance();\n\nconst graphOptions: RGOptions = {\n    layout: {\n        layoutName: 'center'\n    },\n    defaultNodeColor: 'rgb(29, 169, 245)',\n    defaultLineColor: 'rgb(29, 169, 245)',\n    defaultNodeShape: RGNodeShape.circle,\n    defaultNodeWidth: 60,\n    defaultNodeHeight: 60\n};\n```\n\n下面的片段展示了内联数据加载流程，它从一开始就将整棵层级树保存在同一个图实例中。\n\n```tsx\nconst initializeGraph = async () => {\n    const myJsonData: RGJsonData = {\n        rootId: 'a',\n        nodes: [\n            { id: 'a', text: 'a' }, { id: 'b', text: 'b' }, { id: 'b1', text: 'b1' },\n            // ...\n        ],\n        lines: [\n            { id: 'l1', from: 'a', to: 'b' }, { id: 'l2', from: 'b', to: 'b1' },\n            // ...\n        ]\n    };\n    await graphInstance.setJsonData(myJsonData);\n    graphInstance.moveToCenter();\n    graphInstance.zoomToFit();\n};\n```\n\n下面的片段说明，后代收集是从运行时节点对象出发完成的，而不是依赖单独预计算好的查找表。\n\n```tsx\nconst deepGetAllChildIds = (node: RGNode, ids: string[] = []): string[] => {\n    if (ids.includes(node.id)) return ids;\n    ids.push(node.id);\n    for (const cNode of node.lot.childs) {\n        deepGetAllChildIds(cNode, ids);\n    }\n    return ids;\n};\n```\n\n下面的片段展示了在一次节点更新遍历中，所选子树如何保持可见，而非后代节点又如何被压暗。\n\n```tsx\nconst onNodeClick = (nodeObject: RGNode, $event: RGUserEvent) => {\n    const allChildIds = deepGetAllChildIds(nodeObject);\n    graphInstance.getNodes().forEach(node => {\n        if (allChildIds.includes(node.id)) {\n            graphInstance.updateNode(node, { opacity: 1, color: 'rgb(116,2,173)' });\n        } else {\n            graphInstance.updateNode(node, { opacity: 0.1, color: undefined });\n        }\n    });\n};\n```\n\n下面的片段展示了连线高亮如何与节点高亮协同工作，并在最后显式提交。\n\n```tsx\ngraphInstance.getLines().forEach(line => {\n    if (allChildIds.includes(line.from) && allChildIds.includes(line.to)) {\n        graphInstance.updateLine(line, { color: 'rgb(116,2,173)' });\n    } else {\n        graphInstance.updateLine(line, { color: '' });\n    }\n});\ngraphInstance.dataUpdated();\n```\n\n下面的片段展示了共享设置面板如何通过当前激活的图实例实时修改图行为，并启动截图导出。\n\n```tsx\nconst graphInstance = RGHooks.useGraphInstance();\nconst { options } = RGHooks.useGraphStore();\n\n\u003CSettingRow\n    label=\"Wheel Event:\"\n    options={[\n        { label: 'Scroll', value: 'scroll' },\n        { label: 'Zoom', value: 'zoom' },\n        { label: 'None', value: 'none' },\n    ]}\n    value={options.wheelEventAction}\n    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}\n/>\n```\n\n## 这个示例的独特之处\n\n与附近的 `line-hightlight-pro`、`bothway-tree2` 和 `layout-tree` 等示例相比，这个演示始终停留在一个稳定布局上，并将复杂度集中在后代子树检查上。它最有辨识度的动作，是递归遍历 `node.lot.childs`，然后对全图的节点和连线执行一次更新，从而在不改变已加载数据的情况下改变图的解读方式。\n\n这使它不同于那些只标记单个被点击对象、切换树方向，或追加新数据片段的示例。比较记录将这种组合描述为较少见：一个朴素的居中蓝色层级图、运行时后代遍历、聚焦加上下文的淡出效果，以及可通过点击画布重置状态的能力，被压缩进同一个紧凑查看器中。\n\n与只高亮一条连线或只高亮被点击节点的演示相比，它也是一个更适合分支级强调的参考。这里，整个被选中的分支都会保持清晰可读，包括内部连线，而图的其他部分则继续作为上下文保持可见，而不是直接消失。\n\n## 这种模式还能应用到哪里\n\n这种模式很适合迁移到任何需要在不丢失周边上下文的前提下检查某个分支的树结构中。例如组织结构、导航树、权限继承、文件系统浏览器、产品分类树以及物料清单层级。\n\n它也可以作为分析型下钻交互的起点。生产版本可以把占位字母替换为业务记录，将子树高亮与侧边栏详情结合，或者加入用于决定哪些后代保持聚焦的过滤器，同时继续沿用相同的运行时策略：遍历子节点，并原地更新已经加载好的图。\n",false,500,1782615426616]