react+antvX6 制作可编辑图表2——Dnd

2,145 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

项目需求: 最近有一个项目是电路的模拟实验平台,需求为需要有一个工作画布,和一个组件库,实现组件器械的可拖拽、可连接,实验状态可以控制,数据流向展示等功能。就像我们平时课上的虚拟仿真平台一样,又需要像流程图软件一样,可以托拽各种组件到画布中。最后我选择使用antv的X6来制作。

前清提要: 上一期我们讲了如何在react中引入antv x6以及如何创建画布,这一期我们讲一讲antv-X6中的拖拽组件Dnd和Stencil。最开始找了很久,我也是发现了它的这个拖拽组件,感觉就是我想要的样子,于是使用X6来做的。(此前,蚂蚁金服的antd我在别的项目中也是经常使用,样式简洁美观,也很好用,期望他的X6也能同样好用。)

Dnd和Stencil,两者均为x6中的插件,Dnd提供了基础的拖拽能力,Stencil则是在 Dnd 基础上的进一步封装,提供了一个类似侧边栏的 UI 组件,并支持分组、折叠、搜索等能力。接下来讲讲怎么用,走起。

Dnd

Step 1 初始化

引入并创建一个 Dnd 的实例,官方还提供了一些选项来定制拖拽行为。(graph还是需要先引入,可以加在componentDidMount中)

import { Graph, Dom, Addon } from '@antv/x6'
const { Dnd } = Addon
this.dnd = new Dnd({
    target: graph,//绑定画布
    scaled: false,
    animation: true,
    //validateNode用于拖拽结束时,验证节点是否可以放置到目标画布中。
    validateNode(droppingNode, options) {
        return droppingNode.shape === 'html'
            ? new Promise<boolean>((resolve) => {
                const { draggingNode, draggingGraph } = options
                const view = draggingGraph.findView(draggingNode)!
                const contentElem = view.findOne('foreignObject > body > div')
                Dom.addClass(contentElem, 'validating')
                setTimeout(() => {
                    Dom.removeClass(contentElem, 'validating')
                    resolve(true)
                }, 3000)
                })
            : true
        },
})

Step 2 开始拖拽组件

image.png 如上图,左侧即为Dnd创建的可拖拽组件库,右侧为画布,红色圆圈内为拖拽时的矩形。

<div 
    data-type="rect"
    className="dnd-rect"
    onMouseDown={this.startDrag}//绑定
>
Rect
</div>

startDrag = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
    const target = e.currentTarget //获取目标对象
    const type = target.getAttribute('data-type')
    const node =
    type === 'rect'
        ? this.graph.createNode({
            width: 100,//设置矩形拖拽时的样式
            height: 40,
            attrs: {
            label: {
            text: 'Rect',
            fill: '#6a6c8a',
            },
            body: {
            stroke: '#31d0c6',
            strokeWidth: 2,
            },
            },
        })
        : this.graph.createNode({
            width: 60,//设置圆形拖拽时的样式
            height: 60,
            shape: 'html',
            html: () => {
                const wrap = document.createElement('div')
                wrap.style.width = '100%'
                wrap.style.height = '100%'
                wrap.style.display = 'flex'
                wrap.style.alignItems = 'center'
                wrap.style.justifyContent = 'center'
                wrap.style.border = '2px solid rgb(49, 208, 198)'
                wrap.style.background = '#fff'
                wrap.style.borderRadius = '100%'
                wrap.innerText = 'Circle'
                return wrap
            },
        })
    this.dnd.start(node, e.nativeEvent as any)
}

当按下鼠标时调用下面方法开始拖拽,不添加的话将无法实现拖拽功能。

dnd.start(node, e)
选项类型说明
nodeNode开始拖拽的节点。
eMouseEvent JQuery.MouseDownEvent鼠标事件。
  • 开始拖拽时,根据 options.delegateGraphOptions 选项创建一个代理画布,然后使用 start 提供的 node 作为 options.getDragNode 方法的参数,返回一个代理节点(默认克隆),并将代理节点添加到代理画布中。
  • 拖拽过程中,根据鼠标位置实时更新代理画布的在页面中的绝对位置。
  • 拖拽结束时,使用代理节点做为 options.getDropNode 方法的参数,返回一个放置到目标画布的目标节点(默认克隆代理节点),然后调用 options.validateNode 方法来验证节点是否可以被添加到目标画布中,该验证方法支持异步验证,例如发送接口到远端验证或者将新节点插入到数据库。如果通过验证,则将目标节点添加到目标画布中,否则根据 options.animation 选项将代理画布移动到开始拖动的位置,最后销毁代理画布。 成功拖拽到右侧画布中的样子如下图:

image.png

此外还能自定义拖拽节点的样式,也就是拖拽出来的样式可以单独重新定义。

const dnd = new Addon.Dnd({
  getDragNode(node) {
    // 这里返回一个新的节点作为拖拽节点
    return graph.createNode({
      width: 100,
      height: 100,
      shape: 'rect',
      attrs: {
        body: {
          fill: '#ccc'
        }
      }
    })
  }
})