[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:enterprise-quadrant-fixed-layout":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 构建一个使用固定坐标的四象限企业关系图\n\n## 此示例构建了什么\n\n此示例会在画布中心渲染一家公司，并在其四周放置四类关系：投资方、人物、对外投资和分支机构。每个类别都有自己的分组节点、自己的颜色以及自己的连线处理方式，因此最终呈现出来的是一种象限式企业关系图，而不是通用树图。\n\n这里用户不会编辑图结构。主要交互是对手动布局进行实时调参：一个浮动面板允许用户修改连线长度和节点间距，图会立即重新定位。关键点在于，relation-graph 在这里被当作固定坐标的渲染载体使用，而每个节点的坐标都由自定义代码决定。\n\n## 数据如何组织\n\n数据起始于 `initializeGraph()` 中的内联数组：一个根公司，加上四个类别列表。`generateGraphData(...)` 会把这些列表转换为 `RGJsonData`，先创建中心节点、四个分组节点，以及所有叶子节点和连线，然后再加载到图中。\n\n重要的预处理步骤是：初始 JSON 并不包含最终坐标。相反，生成器会添加 `type`、`quadrantKey` 和 `index` 之类的元数据，之后由 `applyLayout(...)` 利用这些元数据计算 x 和 y 坐标。在真实系统中，同样的结构也可以表示股东、高管、子公司、办公室、供应商、分销商，或围绕某个主实体的任意分类网络。\n\n## relation-graph 的使用方式\n\n该图运行在 `fixed` 布局模式下，因此节点位置不是由 relation-graph 自动决定的。`RelationGraph` 被配置为矩形节点、默认无边框、左右连接点，以及默认直线，而生成出来的连线数据会覆盖各条连线的具体路径，从而把中心的曲线连接与叶子节点的正交连线组合起来。\n\n`RGProvider` 提供图上下文，`RGHooks.useGraphInstance()` 是主要的集成入口。示例通过它使用 `setJsonData(...)` 加载 JSON 数据，使用 `getNodes()` 读取当前节点列表，使用 `updateNode(...)` 回写坐标，然后再通过 `moveToCenter()` 和 `zoomToFit()` 让视口居中并适配。共享的 `CanvasSettingsPanel` 也会使用图实例，在运行时修改 `wheelEventAction` 和 `dragEventAction`，并把画布导出为图片。\n\n此示例中没有图插槽，也没有结构编辑工具。额外的 UI 来自共享子组件：`DraggableWindow` 提供浮动外壳，`CanvasSettingsPanel` 提供画布模式切换和图片导出功能。样式则通过生成的类名（如 `my-nodes-invs`）以及针对节点颜色、线标签背景、展开控制器和选中态连线样式的 SCSS 覆盖来自定义。\n\n## 关键交互\n\n两个范围输入驱动了该示例的特定行为。修改 `leafLineLength` 或 `leafNodeGap` 会更新 React state，随后一个 effect 会基于当前图节点重新执行自定义坐标计算。\n\n辅助窗口本身可以拖动、最小化，并可切换到设置视图。在设置视图中，用户可以把滚轮行为切换为滚动、缩放或无；把拖拽行为切换为框选、移动或无；并将当前图导出为图片。\n\n节点和连线点击处理器是存在的，但它们只会记录被点击的对象，不会控制选择、高亮或编辑。\n\n## 关键代码片段\n\n这段配置代码表明，该图被有意设置为固定模式，并使用矩形节点与左右连接点作为默认值。\n\n```tsx\nconst userGraphOptions: RGOptions = {\n    layout: {\n        layoutName: 'fixed'\n    },\n    defaultNodeBorderWidth: 0,\n    defaultNodeShape: RGNodeShape.rect,\n    defaultJunctionPoint: RGJunctionPoint.lr,\n    defaultLineShape: RGLineShape.StandardStraight\n};\n```\n\n这段代码展示了图的结构 JSON 会先被组装出来，同时每个叶子节点都会保留象限元数据，供后续布局阶段使用。\n\n```ts\nnodes.push({\n    ...item,\n    className: `my-nodes-${rule.nodeClassSuffix}`,\n    data: { type: 'leaf', quadrantKey: rule.key, index: index }\n});\n\nlines.push(createLine(rule.rootId, item.id, {\n    text: item.desc || '',\n    color: rule.color,\n    showEndArrow: false,\n    lineShape: RGLineShape.SimpleOrthogonal\n}));\n```\n\n这段坐标代码是手动布局逻辑的核心：它会根据类别让叶子节点向上或向下生长，并把它们放置在分组节点的左侧或右侧。\n\n```ts\nif (rule.isTop) {\n    leafY = groupOriginY - (index * (LEAF_NODE_H + leafNodeGap)) + 30;\n} else {\n    leafY = groupOriginY + (index * (LEAF_NODE_H + leafNodeGap));\n}\n\nconst leafX = rule.isLeft\n    ? groupOriginX - leafLineLength - node.el_W\n    : (groupOriginX + 140) + leafLineLength;\n\nnodesPosition.push({ nodeId: node.id, x: leafX, y: leafY });\n```\n\n这段运行时代码展示了如何将自定义坐标回写到当前的 relation-graph 实例中。\n\n```tsx\nconst nodes = graphInstance.getNodes();\nconst nodesPosition = applyLayout(\n    nodes,\n    leafNodeInitialWidth,\n    leafLineLength,\n    leafNodeGap\n);\nfor (const nodePos of nodesPosition) {\n    graphInstance.updateNode(nodePos.nodeId, {\n        x: nodePos.x,\n        y: nodePos.y\n    });\n}\n```\n\n## 这个示例的独特之处\n\n根据已有的对比数据，这个示例的独特之处在于，它直接展示了确定性的手动坐标分配，而不是 relation-graph 内建布局预设的效果示例。与 `layout-center` 相比，当节点摆放规则来自业务逻辑并且必须以显式 x、y 值回写时，它是更强的起点。与 `bothway-tree` 和 `bothway-tree2` 相比，它并不是在讲树形朝向或分支重样式，而是在讲固定模式下按类别进行四象限摆放。与 `deep-each` 相比，这里真正有意义的实时交互是参数化重布局，而不是点击驱动的焦点高亮。\n\n它少见的组合也很重要：企业关系场景、围绕单个公司节点布置的四个带颜色区分的矩形象限、中心曲线连线加叶子正交带标签连线，以及一个带运行时画布工具的白色浮动辅助窗口。这个组合让它成为一个很实用的桥接示例，适合那些需要为每个类别保留可预测布局区域，而不是接受由库自动选择布局的团队。\n\n## 这种模式还能应用到哪里\n\n这种模式很适合迁移到股东图谱、企业控制关系图、供应商或客户关系概览、分支机构或区域分布图，以及要求每个类别都拥有稳定视觉区域的合规看板。`generateGraphData(...)` 与 `applyLayout(...)` 之间的这种分离方式，也适用于坐标来自外部服务或内部算法，而不是手写四象限规则的场景。\n\n对于把既有布局计算迁移到 relation-graph 的团队来说，这也是一种有用的迁移模式。你可以先生成一次 `RGJsonData`，再通过独立步骤计算坐标，最后把这些坐标推送到当前图实例中，而无需重写渲染层。\n",false,500,1782615388648]