JavaScript is required

图谱画布主题切换

这个示例渲染一棵小型左到右树图,并通过切换容器 class 在三种全画布视觉主题间切换。它展示了如何用 SCSS 给 relation-graph 内置小地图、工具栏、节点和选中态换肤,同时保持同一数据、布局和图实例;共享悬浮面板还提供滚轮模式、拖拽模式和图片导出。

通过一个包装类切换 Relation Graph Canvas 主题

这个示例构建了什么

这个示例会渲染一棵从左到右展开的小型树,并允许用户在运行时在三种视觉主题之间切换整个图的展示效果。图数据和 RelationGraph 渲染器保持不变,但最外层的包装类会从 my-graph-theme-1 切换到 my-graph-theme-3,从而重新设置地图背景、工具栏、节点以及选中状态表面的样式。

界面是一个全高度查看器,画布上方悬浮着一个工具窗口。用户可以拖动该窗口、将其最小化、打开设置浮层、切换当前主题、通过共享设置面板调整滚轮和画布拖拽行为,并将当前图下载为图片。

最重要的一点是,这是一种 CSS 换肤模式,而不是自定义渲染模式。这个示例保持标准树图不变,通过针对 relation-graph 内置 DOM 结构编写 SCSS 选择器,营造出截然不同的视觉氛围。

数据是如何组织的

数据在 initializeGraph() 内以内联方式声明为一个 RGJsonData 对象。它使用一个 rootId、一个包含 idtext 的扁平 nodes 数组,以及一个带有明确 idfromto 字段的扁平 lines 数组。

布局前没有预处理步骤。代码会直接把 JSON 传给 graphInstance.setJsonData(),然后通过 moveToCenter()zoomToFit() 将结果重新居中并适配视图。

这种结构很容易替换为真实业务数据。相同的数据形状可以表示组织树、服务依赖层级、分类树、团队归属关系图,或者审批链,在这些场景里每个节点只需要一个标签,每条边只需要一个源和目标。

relation-graph 是如何使用的

index.tsx 使用 RGProvider 包裹整个示例,因此主图组件和共享辅助窗口都可以解析当前激活的图上下文。在 MyGraph.tsx 中,RelationGraph 只被渲染一次,使用的是一个紧凑的 graphOptions 对象,并且没有自定义节点插槽。

布局使用内置的树形布局,从左向右生长,配置为 treeNodeGapH: 200treeNodeGapV: 10。基础选项刻意让节点和连线样式保持中性:节点是矩形、透明、带轻微边框;连线使用标准曲线形状和左右连接点;工具栏则水平放置在底部居中位置。这样的组合为导入的主题文件留出了空间,让它们定义大部分可见的界面外观。

这个示例在两个地方使用了 RGHooks.useGraphInstance()。在 MyGraph 中,它在组件挂载时加载静态 JSON 数据,然后让图重新居中并适配视图。在共享的 CanvasSettingsPanel 中,它会更新 wheelEventActiondragEventAction 之类的运行时选项,并通过 prepareForImageGeneration()restoreAfterImageGeneration() 执行图片导出流程。

真正的重点在于样式。每个导入的 SCSS 文件都会针对 relation-graph 的选择器编写规则,例如 .rg-map.rg-toolbar.rg-node.rg-node-peel.rg-node-checked.rg-line。由于包装类是在顶层切换的,因此同一个图实例可以在径向渐变玻璃主题、动态多彩主题和动态对角渐变主题之间切换,而不需要修改数据集或重建节点内容。

关键交互

核心交互是主题切换。点击三个 SimpleUISelect 项中的任意一个,都会更新 React 状态,从而替换包装类,并立即为图重新换肤。

悬浮辅助窗口本身也支持交互。它可以在页面上拖动、最小化,并再次展开,这样既能保持图本身界面整洁,又能让控制项随时可用。

设置按钮会打开共享的 CanvasSettingsPanel。在这里,用户可以将鼠标滚轮行为切换为滚动、缩放或无;将画布拖拽行为切换为框选、移动或无;还可以将当前已套用样式的画布下载为图片。这些控件是跨示例复用的通用辅助功能,但在本示例中同样可用,并且会影响当前实时图实例。

主题样式还为节点和连线定义了选中状态下的覆盖样式。这个示例没有添加自定义选择逻辑,但当 relation-graph 应用这些类时,内置的选中视觉效果仍然会继承当前激活主题的样式。

关键代码片段

这段配置展示了图如何保持简单的树形配置,并把最终外观的大部分工作交给 CSS。

const graphOptions: RGOptions = {
    defaultLineColor: 'rgba(255, 255, 255, 0.6)',
    defaultNodeColor: 'transparent',
    defaultNodeBorderWidth: 1,
    defaultNodeBorderColor: 'rgba(255, 255, 255, 0.3)',
    defaultNodeShape: RGNodeShape.rect,
    defaultNodeWidth: 170,
    defaultNodeHeight: 40,
    toolBarDirection: 'h',
    toolBarPositionH: 'center',
    toolBarPositionV: 'bottom',
    defaultPolyLineRadius: 10,

这段初始化流程证明,这个示例使用的是普通 RGJsonData 和图实例 API,而不是自定义加载器。

const myJsonData: RGJsonData = {
    rootId: 'a',
    nodes: [
        { id: 'a', text: 'a' }, { id: 'b', text: 'b' }, { id: 'b1', text: 'b1' },
        // ...
    ],
    lines: [
        { id: 'l1', from: 'a', to: 'b' }, { id: 'l2', from: 'b', to: 'b1' },
        // ...
    ]
};

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

这段渲染代码说明,实时主题切换本质上只是包装类上的一次状态变化,再加上一个小型选择器 UI。

<div className={myGraphClassName} style={{ height: '100vh' }}>
    <DraggableWindow>
        <SimpleUISelect
            data={[
                { value: 'my-graph-theme-1', text: 'Canvas Theme 1' },
                { value: 'my-graph-theme-2', text: 'Canvas Theme 2' },
                { value: 'my-graph-theme-3', text: 'Canvas Theme 3' }
            ]}
            currentValue={myGraphClassName}
            onChange={(newValue: string) => {
                setMyGraphClassName(newValue);
            }}
        />
    </DraggableWindow>

这段主题代码片段展示了包装类如何深入 relation-graph 的内置表面进行样式设置,而不是用自定义插槽替换它们。

.my-graph-theme-2 {
    .relation-graph {
        .rg-map {
            background: radial-gradient(circle at center, #aff69e 0%, #9ed8f6 10%, #f69ee5 20%, #d4980e 30%, #daa93a 40%, #3eda3a 50%, #b9ed94 60%);
            background-size: 1000% 1000%;
            animation: backgroundGradient 30s ease infinite;
        }

        .rg-toolbar {
            background-color: #ffffff;
        }

这段辅助代码片段展示了设置浮层如何直接更新图的交互模式,并通过共享图实例触发图片导出。

<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 }); }}
/>
<SettingRow
    label="Canvas Drag Event:"

这个示例的独特之处

比较数据将这个示例与 css-themegenerate-image-with-backgroundnode-style3node-drag-handle 放得较近,但它的关注点比这些相邻示例都更集中。它的独特价值在于展示:同一棵 relation-graph 树只需要切换一个外层包装类,就能在三套导入的全画布皮肤之间切换,同时保持相同的数据、布局和内置渲染器不变。

css-theme 相比,这个示例是更聚焦的包装类换肤参考。它没有加入 minimap、自定义节点图标插槽或更大的主题画廊,而是专注于营造氛围感渐变画布、半透明矩形文本节点以及工具栏换肤。

generate-image-with-background 相比,这里的主要经验是实时展示切换,而不是捕获预览工作流。与 node-style3node-drag-handle 相比,它更接近 relation-graph 的默认查看器行为,并把 CSS 表面主题化作为示例核心,而不是基于插槽驱动的节点内容或手动拖拽交互。

这种模式还能用在哪里

这种模式非常适合品牌化仪表盘场景,在这些场景中,同一张图需要针对不同客户、活动或工作区模式提供多套视觉皮肤。它也很适合白标产品,因为这类产品通常需要保持同一个图实现,只在 CSS 层交换展示效果。

它也适用于需要临时展示模式的内部工具,例如白天与夜间运行视图、按状态着色的层级仪表盘,或者面向演示和截图的图界面。在这些场景中,数据模型和图行为可以保持稳定,而包装类则负责控制可见的视觉氛围。