XFlow

908 阅读2分钟

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:待完善