JavaScript is required

画布拖拽与滚轮模式切换

这个示例构建了一个全高度的 relation-graph 工作区,用来测试画布对拖拽和滚轮输入的响应方式。图本身是刻意保持通用的:一个居中的示例网络、使用云朵图标渲染节点、带有一个浮动的白色工具窗口,并且没有任何会分散注意力的业务领域叠加层。

浮动画布工作区中的实时拖拽与滚轮模式

这个示例构建了什么

这个示例构建了一个全高度的 relation-graph 工作区,用来测试画布对拖拽和滚轮输入的响应方式。图本身是刻意保持通用的:一个居中的示例网络、使用云朵图标渲染节点、带有一个浮动的白色工具窗口,并且没有任何会分散注意力的业务领域叠加层。

用户可以在运行时将滚轮模式切换为滚动、缩放或无操作,将画布拖拽模式切换为选择、移动或无操作,点击空白画布区域清除选中状态,通过标题栏拖动辅助窗口,并将当前图视图导出为图片。这个示例的重点不在示例数据,而在于围绕 relation-graph 交互行为构建的运行时控制界面。

数据如何组织

数据直接在 initializeGraph() 内以内联方式声明为一个 RGJsonData 对象。它设置了 rootId: '2',定义了 24 个硬编码节点,以及 23 条硬编码连线。这个结构通过 setJsonData(...) 直接加载,然后在视口中居中并适配显示。

在调用 setJsonData(...) 之前没有任何预处理流程。这个示例不会转换外部数据,不会派生展示状态,也不会在交互模式变化时重建数据集。在真实应用中,这种同样的数据形状可以作为轻量级拓扑示例、用于手势规则的 QA 固件,或者一个让用户在稳定图内容上尝试工作区控制方式的入门沙盒。

relation-graph 如何使用

index.tsxRGProvider 包裹页面,MyGraph.tsx 则使用 RGHooks.useGraphInstance() 来加载数据并清除选中状态。图的选项配置很少,但表达明确:debug: falsedefaultJunctionPoint: RGJunctionPoint.border,以及 layout.layoutName = 'center'。组件挂载后,示例通过 setJsonData(...) 加载内联图数据,然后调用 moveToCenter()zoomToFit()

RelationGraph 绑定了 onCanvasClick,并挂载了一个 RGSlotOnNode 渲染器,用居中的 Cloud 图标替换节点内容。这个示例没有使用连线、画布或视口插槽,也没有实现编辑或创作行为。运行时交互控制放在共享的 CanvasSettingsPanel 中,它使用 RGHooks.useGraphStore() 读取当前的 dragEventActionwheelEventAction,然后通过 graphInstance.setOptions(...) 更新已挂载图上的这些值。同一个面板还使用 prepareForImageGeneration()getOptions()restoreAfterImageGeneration() 来支持图片导出。

浮动工具外壳是一个共享的 React 组件,而不是 relation-graph 自带的功能,但它对整体模式很重要。DraggableWindow 在 React state 中维护自己的屏幕位置,从标题栏开始拖拽,并通过文档级的 mousemovemouseup 监听器更新窗口位置。本地样式表非常精简,基本保持 relation-graph 的选择器不变,因此可见的定制主要来自共享的浮动窗口和自定义节点插槽。

关键交互

  • 点击空白画布会调用 clearChecked(),因此画布可以作为清除任意图选中状态的重置手势。
  • 浮动设置面板通过 useGraphStore() 反映图当前的滚轮模式和拖拽模式,因此控制状态始终与正在运行的 relation-graph 实例保持一致。
  • 点击滚轮模式按钮后,会立即改变画布处理滚轮输入的方式,而不需要重新加载数据或重新挂载图。
  • 点击拖拽模式按钮后,会立即改变画布拖动时是执行选择、移动,还是不执行任何操作。
  • 工具窗口本身可以通过标题栏拖动,因此这个控制界面可以移动,而不会固定遮挡图的某个区域。
  • 下载操作会先让图画布进入可捕获状态,将其渲染为图片 blob,下载文件,然后恢复图状态。

关键代码片段

这个片段展示了该示例使用居中布局和边界连接点,并让图选项专注于工作区行为:

const graphOptions: RGOptions = {
    debug: false,
    defaultJunctionPoint: RGJunctionPoint.border,
    layout: {
        layoutName: 'center'
    }
};

这个片段展示了示例图以内联方式组装,并在初始化期间一次性加载:

const myJsonData: RGJsonData = {
    rootId: '2',
    nodes: [
        { id: '1', text: 'Node-1' },
        { id: '2', text: 'Node-2' },
        { id: '3', text: 'Node-3' },
        // ...
    ],
    lines: [
        { id: 'l1', from: '7', to: '71' },
        // ...
    ]
};
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();

这个片段展示了空白画布点击被绑定为清除选中状态,而不是用于日志输出或选择几何处理:

const onCanvasClick = () => {
    graphInstance.clearChecked();
};

这个片段展示了节点渲染被自定义插槽覆盖,因此图整体视觉保持中性,并以图标为主:

<RelationGraph options={graphOptions} onCanvasClick={onCanvasClick}>
    <RGSlotOnNode>
        {({ node }: RGNodeSlotProps) => (
            <div className="flex items-center justify-center h-full p-2">
                <Cloud size={30} color="#666666" />
            </div>
        )}
    </RGSlotOnNode>
</RelationGraph>

这个片段展示了运行时模式切换的方式:面板从图 store 中读取当前值,并通过 setOptions(...) 推送更新:

const { options } = RGHooks.useGraphStore();
const dragMode = options.dragEventAction;
const wheelMode = options.wheelEventAction;

<SettingRow
    label="Wheel Event:"
    value={wheelMode}
    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}
/>

这个片段展示了对应的拖拽模式控制,它在不触碰数据集的情况下改变画布行为:

<SettingRow
    label="Canvas Drag Event:"
    options={[
        { label: 'Selection', value: 'selection' },
        { label: 'Move', value: 'move' },
        { label: 'none', value: 'none' },
    ]}
    value={dragMode}
    onChange={(newValue: string) => { graphInstance.setOptions({ dragEventAction: newValue }); }}
/>

这个片段展示了导出流程:先让 relation-graph 进入可捕获状态,将画布 DOM 渲染为图片,再在之后恢复图状态:

const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
    graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
    backgroundColor: graphBackgroundColor
});
await graphInstance.restoreAfterImageGeneration();

这个片段展示了浮动辅助窗口如何通过捕获标题栏上的鼠标按下事件,并结合文档级事件更新位置,从而实现可拖拽:

const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
    if (!draggable || !windowRef.current) return;
    if (e.button !== 0) return;

    const currentLeft = windowRef.current.offsetLeft;
    const currentTop = windowRef.current.offsetTop;
    setPosition({ x: currentLeft, y: currentTop });
    dragInfoRef.current = {
        isDragging: true,
        relX: e.clientX - currentLeft,
        relY: e.clientY - currentTop
    };
};

这个示例的独特之处

对比数据把这个示例归类到 canvas-eventselectionscanvas-selectiongee-thumbnail-diagramswitch-layout 附近,但它的关注点比这些示例都更窄。相较于 canvas-event,它并不讲解拖拽生命周期回调或可视化拖拽指示。相较于 selectionscanvas-selection,它不会消费选择几何信息、标记已选节点,或创建新的图内容。相较于 gee-thumbnail-diagramswitch-layout,它也不聚焦于缩略图导航或布局切换。

它的突出之处在于将可拖动的覆盖面板、从图 store 实时反映 dragEventActionwheelEventAction、对已挂载图即时执行 setOptions(...) 更新、点击画布清除状态、图片导出,以及一个刻意保持中性的居中布局示例图组合在一起。在带有浮动辅助窗口的示例中,这个示例把图的视觉设计压得非常简单,从而让输入模式控制成为主要教学内容。

这一模式还能应用在哪里

这一模式非常适合那些需要把画布行为作为用户可配置项,而不是写死在实现中的图工作区。典型场景包括拓扑巡检工具、调查分析面板、用于平移与缩放规则培训的沙盒,以及用于验证手势策略在实时图上手感的 QA 页面。

它也适合那些已经有稳定图数据集,但仍需要围绕图提供辅助控制界面的产品。同样的方法还可以扩展为无障碍模式切换、面向不同操作员的交互预设、截图工具,或者允许用户在浏览、移动和选择行为之间切换而无需重建图的支持控制台。