XFlow 是 AntV 旗下, 基于 X6 图编辑引擎、面向 React 技术栈用户的图编辑应用级解决方案, 旨在让复杂的图编辑应用开发简单高效。
类比antd体系, X6 是图编辑场景的 antd, 提供图编辑的各种原子能力。而 XFlow 是图编辑场景的 ProComponent, 通过 App 扩展系统/状态管理/命令模式来实现对 X6 的原子能力的组合封装, 最终实现应用级场景的开箱即用。
安装
XFlow 通过 npm 或 yarn 命令安装。
#npm
$ npm install @antv/xflow --save
#yarn
$ yarn add @antv/xflow
项目入口
import React,{useRef} from 'react'
/** app 核心组件 */
import { XFlow, XFlowCanvas, KeyBindings } from '@antv/xflow'
import type { IApplication, IAppLoad } from '@antv/xflow'
/** 交互组件 */
import {
/** 触发Command的交互组件 */
CanvasScaleToolbar,
NodeCollapsePanel,
/** Graph的扩展交互组件 */
CanvasSnapline,
CanvasNodePortTooltip,
DagGraphExtension,
} from '@antv/xflow'
/** app 组件配置 */
/** 配置画布 */
import { useGraphHookConfig } from './config-graph'
/** 配置Dnd组件面板 */
import * as dndPanelConfig from './config-dnd-panel'
import './index.less'
import '@antv/xflow/dist/index.css'
export interface IProps {
meta: { flowId: string }
}
export const Demo: React.FC<IProps> = props => {
//通过外部数据更新画布
const { graphData } = props
const graphHooksConfig = useGraphHookConfig(props)
const [appRef, setAppRef] = useState(null);
/**
* @param app 当前XFlow工作空间
* @param extensionRegistry 当前XFlow配置项
*/
const onLoad: IAppLoad = async app => {
setAppRef(app)
//获取到实例之后可以绑定一些监听事件或者做一些其他的事情
//参考x6事件系统:https://x6.antv.vision/zh/docs/tutorial/intermediate/events
//graph.on(node:dblclick, ({ node }) => {
// TODO:....
//});
}
useEffect(() => {
if (appRef && graphData?.nodes?.length) {
appRef.executeCommand(XFlowGraphCommands.GRAPH_RENDER.id, {
graphData: temp,
});
}
}, [appRef,graphData]);
return (
<XFlow
className="dag-user-custom-clz"
hookConfig={graphHooksConfig}
>
<DagGraphExtension />
<NodeCollapsePanel
className="xflow-node-panel"
searchService={dndPanelConfig.searchService}
nodeDataService={dndPanelConfig.nodeDataService}
onNodeDrop={dndPanelConfig.onNodeDrop}
position={{ width: 230, top: 0, bottom: 0, left: 0 }}
footerPosition={{ height: 0 }}
bodyPosition={{ top: 40, bottom: 0, left: 0 }}
/>
<XFlowCanvas position={{ top: 40, left: 230, right: 290, bottom: 0 }}>
<CanvasScaleToolbar position={{ top: 12, right: 12 }} />
<CanvasSnapline color="#faad14" />
<CanvasNodePortTooltip />
</XFlowCanvas>
</XFlow>
)
}
export default Demo
配置节点或边
import React from 'react'
import type { NsGraph } from '@antv/xflow'
import { NsGraphStatusCommand } from '@antv/xflow'
//对应样式文件
import './algo-node.less'
//完全自定义即可
export const AlgoNode: NsGraph.INodeRender = props => {
return (
<div className={`xflow-algo-node ${props.xxx ? 'panel-node' : ''}`}>
// xxxx
</div>
)
}
配置hookConfig
import type { IProps } from './index'
import type { NsGraph, NsNodeCmd } from '@antv/xflow'
import { XFlowNodeCommands } from '@antv/xflow'
import { createHookConfig, DisposableCollection } from '@antv/xflow'
import { DND_RENDER_ID, GROUP_NODE_RENDER_ID } from './constant'
//为自定义组件 是正常的react组件即可
import { AlgoNode } from './react-node/algo-node'
import { GroupNode } from './react-node/group'
export const useGraphHookConfig = createHookConfig<IProps>((config, proxy) => {
// 获取 Props
const props = proxy.getValue()
console.log('get main props', props)
config.setRegisterHook(hooks => {
const disposableList = [
// 注册增加 react Node Render
hooks.reactNodeRender.registerHook({
name: 'add react node',
handler: async renderMap => {
// 注册节点或者边
renderMap.set(DND_RENDER_ID, AlgoNode)
renderMap.set(GROUP_NODE_RENDER_ID, GroupNode)
},
}),
// 注册修改graphOptions配置的钩子
hooks.graphOptions.registerHook({
name: 'custom-x6-options',
after: 'dag-extension-x6-options',
handler: async options => {
//设置画布配置项
options.grid = false
options.keyboard = {
enabled: true,
}
},
}),
// 也可以在这个地方注册增加 graph event
// hooks.x6Events.registerHook({
// name: 'add',
// handler: async events => {
// events.push({
// eventName: 'node:moved',
// callback: (e, cmds) => {
//
// },
// } as NsGraph.IEvent<'node:moved'>)
// },
// }),
]
const toDispose = new DisposableCollection()
toDispose.pushAll(disposableList)
return toDispose
})
})
配置 dndPanelConfig
/* eslint-disable @typescript-eslint/no-unused-vars */
import { uuidv4 } from '@antv/xflow'
import { XFlowNodeCommands } from '@antv/xflow'
import { DND_RENDER_ID } from './constant'
import type { NsNodeCmd } from '@antv/xflow'
import type { NsNodeCollapsePanel } from '@antv/xflow'
import { Card } from 'antd'
import React from 'react'
export const onNodeDrop: NsNodeCollapsePanel.IOnNodeDrop = async (node, commands, modelService) => {
const args: NsNodeCmd.AddNode.IArgs = {
nodeConfig: { ...node, id: uuidv4() },
}
commands.executeCommand(XFlowNodeCommands.ADD_NODE.id, args)
}
const NodeDescription = (props: { name: string }) => {
return (
<Card size="small" title="算法组件介绍" style={{ width: '200px' }} bordered={false}>
欢迎使用:{props.name}
这里可以根据服务端返回的数据显示不同的内容
</Card>
)
}
export const nodeDataService: NsNodeCollapsePanel.INodeDataService = async (meta, modelService) => {
console.log(meta, modelService)
return [
{
id: '数据读写',
header: '数据读写',
children: [
{
id: '2',
label: '算法组件1',
parentId: '1',
renderKey: DND_RENDER_ID,
popoverContent: <NodeDescription name="算法组件1" />,
},
{
id: '3',
label: '算法组件2',
parentId: '1',
renderKey: DND_RENDER_ID,
popoverContent: <NodeDescription name="算法组件2" />,
},
{
id: '4',
label: '算法组件3',
parentId: '1',
renderKey: DND_RENDER_ID,
popoverContent: <NodeDescription name="算法组件3" />,
},
],
}
]
}
export const searchService: NsNodeCollapsePanel.ISearchService = async (
nodes: NsNodeCollapsePanel.IPanelNode[] = [],
keyword: string,
) => {
const list = nodes.filter(node => node.label.includes(keyword))
console.log(list, keyword, nodes)
return list
}
至此一个最基本的demo就完成了
TODO:待完善