JavaScript is required

自定义节点企业服务宣传卡片

这个示例把固定布局 `RelationGraph` 当作展示画布,通过 `RGSlotOnNode` 渲染一张超大企业宣传卡。它还复用共享悬浮辅助窗提供滚轮/拖拽模式切换和图片导出,更适合作为图谱承载品牌化 UI 的参考,而非布局变化示例。

将 relation-graph 用作固定式宣传卡片画布

这个示例构建了什么

这个示例构建了一个全屏 RelationGraph 视图,它的行为更像是一个展示面板,而不是关系图。示例自带的数据只包含一个根节点且没有连线,这个根节点会被替换为一张 1200 x 600 的企业会员卡片,其中包含品牌文案、服务要点、外部链接,以及一个次级的 3D 内嵌卡片。

用户可以拖动或最小化浮动辅助窗口、打开设置浮层、切换滚轮与画布拖拽行为,并将当前画布导出为图片。这个示例最值得研究的重点并不是布局变化,而是如何把一个固定布局的图,当作承载超大自定义 React 组合内容的宿主画布。

数据是如何组织的

图数据直接在 initializeGraph() 中以内联方式声明为一个很小的 RGJsonData 对象,其中包含 rootId: 'root'、一个节点,以及一个空的 lines 数组。渲染图之前,没有获取数据的步骤、没有外部转换层,也没有额外的派生定位处理。

唯一真正的预处理步骤是执行顺序控制:代码会等待 setJsonData() 完成,然后再调用 moveToCenter()zoomToFit(),以便在 relation-graph 完成节点渲染尺寸测量后再调整视口。在生产系统中,同样的结构可以表示落地页风格的产品面板、品牌化门户入口、仪表盘头图卡片,或任何其他仍然能从图视口控制与导出能力中受益的单一焦点卡片。

relation-graph 是如何使用的

index.tsx 使用 RGProvider 包裹页面,这让图组件和浮动辅助窗口都可以从同一个图上下文中使用 relation-graph hooks。在 MyGraph.tsx 中,RGHooks.useGraphInstance() 是主要的集成入口:它会加载内联数据、在挂载后将视图居中,并让视口适配内容。

图选项将画布保持在 layoutName: 'fixed',并使用矩形节点、正交默认连线、上下连接点、零节点边框,以及中性的兜底颜色。这个固定布局很重要,因为该示例并不是让 relation-graph 去计算一个网络布局,而是把图当作承载编排内容的稳定画布。

主要的渲染定制点是 RGSlotOnNode。当插槽遇到 root 节点时,它会渲染 RootNodeContent;否则会回退为一个简单的带边框文本块。尽管导入了一些相关符号,但实际交付的渲染路径中并没有启用中的画布或视图插槽。

共享的查看器工具来自 DraggableWindow 及其 CanvasSettingsPanel。这个辅助组件会通过 RGHooks.useGraphStore() 读取当前交互模式,再通过 graphInstance.setOptions(...) 在运行时更新这些模式,并借助 prepareForImageGeneration()getOptions()restoreAfterImageGeneration() 以及共享的 modern-screenshot 封装来导出图像。样式分为两部分:示例自身的 SCSS 用于重设图内部元素(如线标签和 logo 填充)的主题;可复用的 Simple3DCard 样式则为内嵌卡片增加悬停倾斜、辉光和全息网格效果。

关键交互

  • 浮动辅助窗口可以通过标题栏拖动。
  • 辅助窗口可以最小化和展开。
  • 设置按钮会在辅助窗口内打开一个浮层面板,点击半透明背景会关闭它。
  • 设置面板可以将滚轮行为切换为 scrollzoomnone
  • 设置面板可以将画布拖拽行为切换为 selectionmovenone
  • Download Image 操作会先准备图 DOM、截取当前视图、下载图片,然后恢复图状态。
  • 自定义根卡片中包含指向 https://relation-graph.com/services 的外部链接。
  • 节点和连线点击处理器已经接入,但在示例自带的数据集中,它们只会记录对象,而且没有可交互的连线。

关键代码片段

这段代码表明,该示例有意让 relation-graph 保持在固定模式,并将其用作手工组合内容的承载面。

const graphOptions: RGOptions = {
    debug: false,
    layout: {
        layoutName: 'fixed'
    },
    defaultNodeShape: RGNodeShape.rect,
    defaultLineShape: RGLineShape.StandardOrthogonal,
    defaultJunctionPoint: RGJunctionPoint.tb,
    defaultNodeBorderWidth: 0,
    defaultLineColor: '#666',
    defaultNodeColor: '#fff'
};

这段代码证明,数据集是在本地构建的,并且会在视口居中与适配前先完成加载。

const initializeGraph = async () => {
    const myJsonData: RGJsonData = {
        rootId: 'root',
        nodes: [
            { id: 'root', text: 'Node' }
        ],
        lines: []
    };

    await graphInstance.setJsonData(myJsonData);
    graphInstance.moveToCenter();
    graphInstance.zoomToFit();
};

这个插槽渲染器是核心技巧:在保留其他节点兜底渲染方式的同时,把根节点替换为完整的自定义 React 布局。

<RGSlotOnNode>
    {({ node }) => {
        return (
            node.id === 'root' ? (
                <RootNodeContent />
            ) : (
                <div className="px-2 min-w-[100px] rounded border border-gray-500 flex items-center justify-center w-full h-full text-sm text-slate-800 font-medium select-none">
                    {node.text}
                </div>
            )
        );
    }}
</RGSlotOnNode>

这段摘录展示了,辅助浮层会直接在运行中的实例上修改 relation-graph 的交互模式,而不是重新构建图数据。

<SettingRow
    label="Wheel Event:"
    options={[
        { label: 'Scroll', value: 'scroll' },
        { label: 'Zoom', value: 'zoom' },
        { label: 'None', value: 'none' },
    ]}
    value={wheelMode}
    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}
/>

这个导出流程展示了,示例如何在截图前准备 relation-graph,把画布 DOM 交给截图辅助函数,并在结束后恢复图状态。

const downloadImage = async () => {
    const canvasDom = await graphInstance.prepareForImageGeneration();
    let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
    if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
        graphBackgroundColor = '#ffffff';
    }
    const imageBlob = await domToImageByModernScreenshot(canvasDom, {
        backgroundColor: graphBackgroundColor
    });
    if (imageBlob) {
        downloadBlob(imageBlob, 'my-image-name');
    }
    await graphInstance.restoreAfterImageGeneration();
};

这个示例的独特之处

根据对比数据,这个示例的特殊之处在于:它把图拓扑维持得极小,却把几乎所有复杂度都集中到一个通过插槽渲染的根节点中。它的独特组合包括固定布局初始化、挂载时 setJsonData() 后再执行居中与适配、使用 RGSlotOnNode 完整替换节点内容、嵌入外部链接、复用 Simple3DCard,以及在同一屏幕中使用共享的浮动设置与导出外壳。

table-relationship 相比,这里的自定义 HTML 不是结构性的,也不承担行级端点或可见数据连线的职责。与 css-theme 相比,重点不在于运行时切换图主题,而在于一个预先编排好的品牌化卡片。与 use-dagre-layout-2 相比,这里的固定布局只是一个容器;并不存在二次布局算法、间距控制或由 minimap 驱动的工作流。

对比记录还指出了一个重要的反向结论:不应把它描述为一个真正的多布局示例。实际交付的实现是在 layoutName: 'fixed' 下使用一个根节点,真正持久的启示是“将图作为展示画布进行组合”,而不是布局变化。

这种模式还能用在哪里

这种模式非常适合这样的场景:图画布需要承载一个占主导地位的自定义界面,而不是一个密集的关系图。示例包括企业升级面板、产品能力概览、发布或营销卡片、引导检查点,以及仍然希望保留图平移、缩放行为和图片导出能力的仪表盘头部模块。

当团队希望尽早引入图基础设施,而更丰富的拓扑结构尚未出现时,它也很有用。像这样单卡片、固定布局的示例,今天可以作为品牌化入口状态、高级功能说明,或门户卡片存在,之后再逐步演变为更具连接性的图体验,而无需更换底层查看器外壳。