JavaScript is required

D3 Treemap/Pack 布局接入

这个示例在 `fixed` 模式加载静态关系图,从实时图树重建 D3 层级,再把 treemap 或 circle-pack 几何结果回写到 relation-graph 节点。悬浮选择器可在同一图上切换这两种 D3 布局,同时保留自定义节点插槽、小地图、共享画布设置和图片导出。

在 relation-graph 中切换 D3 Treemap 与 Circle-Pack 布局

这个示例构建了什么

这个示例构建了一个全高度的层级结构演示场景,在其中,同一份已加载的关系图可以通过两种不同的 D3 层级算法重新排布。画布展示的是一棵通用树形节点结构,悬浮说明面板提供布局选择器,内置 mini-view 则持续作为当前排布的总览可见。

关键的视觉结果是:同一张图无需替换 relation-graph 场景本身,就可以在 treemap 风格的矩形与 circle-pack 气泡之间切换。自定义节点渲染、连线标签、minimap 以及共享画布工具都保持可用,变化的只有布局计算。

数据如何组织

该示例从 initializeGraph() 内部的一个静态内联 RGJsonData 对象开始。它声明了一个 rootId、一个扁平的 nodes 数组和一个扁平的 lines 数组,然后在将数据加载进图之前,用 L${index} 为缺失的连线 id 回填。

这个内联 JSON 只是数据流的第一阶段。在执行 setJsonData(...) 之后,示例会向实时 graph 实例请求根节点,并基于 rootNode.lot.childs 重新构建一个适配 D3 的层级结构。叶子节点会得到 value: 1,内部节点会得到 value: 0,而这个派生出来的树就会成为 treemap(...)pack() 的输入。

在真实应用中,同样的模式可以从产品分类、文件系统文件夹、组织层级、依赖分组,或任何其他层级结构出发。适用场景是:你希望保留 relation-graph 的渲染能力,但更倾向于使用外部布局引擎来计算几何信息。

relation-graph 是如何使用的

RGProvider 包裹整个页面,使 RGHooks.useGraphInstance() 能够解析到当前激活的 graph 实例。图本身配置为 layout.layoutName = 'fixed',这是这个演示的关键设置:relation-graph 负责渲染和管理场景,但最终的坐标与节点尺寸由 D3 这一步生成,而不是来自内置布局预设。

组件先通过 setJsonData(...) 加载内联图数据,然后立即调用一个自定义的 doMyLayout() 辅助函数。这个辅助函数通过 getNodeById('root') 取得实时根节点,将其传给 applyD3TreemapOrPackLayout(...),然后再用 moveToCenter()zoomToFit() 对结果做居中与适配。

D3 集成发生在 MyGraphLayout4D3Treemap.ts 中。它会基于实时的 relation-graph 节点重建层级输入,对当前 relation-graph 视图尺寸运行 treemap(...).tile(treemapSquarify)pack(),按尺寸对计算后的节点排序,然后通过 updateNode(...) 把宽度、高度、位置、z-index 以及 node-shape 的变化写回去。这是该示例最核心的教学点:relation-graph 仍然是运行时图形承载面,而 D3 则成为一个可替换的几何计算引擎。

在这个过程中,slots 始终得到保留。RGSlotOnNode 负责渲染自定义节点主体,为根节点提供更强的视觉强调,并通过圆角矩形或完全圆形来呼应当前布局模式。RGSlotOnView 挂载了 RGMiniView,因此即使节点几何来自 D3,概览地图依然可用。样式还在 my-relation-graph.scss 中做了进一步细化,把连线标签变成紧凑的白色徽标。

悬浮外壳来自共享辅助组件,而不是示例本地的图代码。DraggableWindow 提供说明面板、拖拽与最小化行为、设置覆盖层以及图片导出功能。SimpleUISelect 则用作两个布局选项之间的切换器。

关键交互

  • 布局选择器会在 rectcircle 之间切换 layoutShape,每次变化都会在已加载的 graph 实例上重新运行外部布局。
  • 在首次挂载时,示例会加载数据并立刻执行第一次 D3 布局,因此用户不会看到一个尚未排布的 fixed-layout 场景。
  • 点击画布空白区域会通过 graphInstance.clearChecked() 清除 graph 的选中状态。
  • 悬浮辅助窗口可以被拖拽、最小化,也可以展开成设置覆盖层。
  • 设置覆盖层会通过 setOptions(...) 修改画布滚轮模式和拖拽模式,并且可以将当前图导出为图片。
  • RGMiniView 为重新排布后的场景提供概览导航。

关键代码片段

这个片段说明,示例有意让 relation-graph 保持在 fixed 模式下,以便由 D3 提供最终几何信息。

const graphOptions: RGOptions = {
    debug: false,
    layout: {
        layoutName: 'fixed'
    },
    defaultNodeShape: RGNodeShape.rect,
    defaultLineShape: RGLineShape.StandardCurve
};

这个片段展示了两阶段加载流程:先加载图数据,再针对实时 graph 实例运行自定义布局。

myJsonData.lines.forEach((line, index) => {
    if (!line.id) line.id = `L${index}`;
});

await graphInstance.setJsonData(myJsonData);
await doMyLayout();

这个片段证明,运行时切换是由组件状态驱动的,而不是通过重建另一份图数据集实现的。

useEffect(() => {
    doMyLayout();
}, [layoutShape]);

<SimpleUISelect
    data={[
        { value: 'rect', text: 'Rectangle Treemap' },
        { value: 'circle', text: 'Circle Pack' }
    ]}

这个片段是 D3 交接的核心:辅助函数会基于实时节点重建层级结构,并依据所选模式决定使用 treemap 还是 pack。

const treemapInput = buildTreemapDataNode(rootNode);
let layout;
if (shape === 'rect') {
    layout = treemap<TreemapDataNode>()
        .size([graphInstance.options.viewSize.width, graphInstance.options.viewSize.height])
        .padding(padding)
        .tile(treemapSquarify);
} else {
    layout = pack()
        .size([graphInstance.options.viewSize.width, graphInstance.options.viewSize.height]);
}

这个片段展示了如何把 D3 计算出的几何结果写回实时 relation-graph 节点中。

if (rgNode) {
    const nodeProps: Partial<RGNode> = {};
    if (shape === 'rect') {
        nodeProps.nodeShape = 1;
        nodeProps.x = n.x - offsetX;
        nodeProps.y = n.y - offsetY;
    } else {
        nodeProps.nodeShape = 0;
        nodeProps.x = n.x - offsetX - n.width / 2;
        nodeProps.y = n.y - offsetY - n.height / 2;
    }
    nodeProps.width = n.width;
    nodeProps.height = n.height;
    graphInstance.updateNode(n.id, nodeProps);
}

这个片段展示了共享工具外壳还在布局演示之上提供了图片导出功能。

const canvasDom = await graphInstance.prepareForImageGeneration();
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
    backgroundColor: graphBackgroundColor
});
if (imageBlob) {
    downloadBlob(imageBlob, 'my-image-name');
}
await graphInstance.restoreAfterImageGeneration();

这个示例的独特之处

准备好的对比数据表明,这个示例与 use-dagre-layoutuse-dagre-layout-2layout-tree 较为接近,但它的重点不同。与 Dagre 示例相比,它更强调面向层级结构的几何重映射,而不是有向图的摆放。它会从 rootNode.lot.childs 重建 D3 层级结构,并写回节点宽度、高度、z-index 和形状,而不只是对已测量节点做位置调整。

use-dagre-layout-2 相比,它的独特之处不在于对单一算法进行参数调优,而是在同一个实时 graph 实例上于两类外部层级布局之间做运行时切换。与 layout-tree 相比,它的独特之处也不在于内置树方向切换,而在于展示第三方布局引擎如何驱动 relation-graph,同时保留 slots、minimap 支持、连线标签样式以及共享查看器外壳。

对比记录和稀有性记录支持一个克制的总结:当项目需要在一个紧凑查看器中同时具备 fixed-mode 启动、实时树到 D3 层级结构重建、运行时 treemap 与 pack 切换以及几何回写时,这是一个很好的起点。它不应被描述为唯一的外部布局示例,也不应被描述为通用编辑器示例。

这种模式还适用于哪里

这种模式非常适合那些本身已经拥有层级数据、但在节点几何上希望获得超出 relation-graph 内置布局能力的应用。典型例子包括分类浏览器、存储或文件夹总览、投资组合分解、能力地图,以及同一份层级数据可能既需要紧凑打包视图也需要盒状视图的组织结构摘要。

它同样适合作为一种集成模板。团队可以继续使用 relation-graph 提供节点 slots、覆盖层、画布行为和导航能力,同时接入另一个层级引擎,由后者在外部计算坐标和尺寸,再把结果写回实时 graph 中。