Home

Awesome

antv-x6-vue

核心思想

  1. 由于x6主要面向编辑场景,所以对每一个节点有更多的交互逻辑。所以,将x6的Shape抽象成组件,每一个组件负责管理自己的生命周期。
  2. 针对复杂的自定义图形,利用x6支持渲染vue组件@antv/x6-vue-shape的功能,同时利用slots将节点渲染交给当前组件,将图形相关逻辑交给x6。
import { VueShape as VueShapeContainer } from '@antv/x6-vue-shape';

cell.value = new VueShapeContainer({
  id, width, height,
  primer, useForeignObject,
  // 这里将自己的slots中的内容强行放到画布中去
  // 这样图结构的交互还有一些操作逻辑交给x6
  // 通过vue绘制的组件渲染和组件内部交互逻辑交给用户
  component: component
    ? component
    : () => h('div', {key: id, class: 'vue-shape'}, slots.default ? slots.default({props, item: cell}) : null),
  ...otherOptions,
})
graph.addCell(cell.value)

image

  1. 提供useVueShape,可以很容易的自定义一个vue组件定制出来的节点。
  2. 提供useCellEvent,可以比较方便的给当前节点绑定事件。
const CustomNode = defineComponent({
  name: 'CustomNode',
  props: [...VueShapeProps, 'otherOptions'],
  inject: [contextSymbol],
  setup(props, context) {
    // 直接传递props给useVueShape,watch的时候不能监听到变化
    const cell = useVueShape(() => props, context)
    useCellEvent('node:click', (e) => context.emit('click', e), { cell })
    return () => null
  }
})
  1. 提供useTeleport,优化x6-vue-shape默认创建多个App导致渲染性能问题。同时避免出现节点数据更新不及时问题。

安装

yarn add antv-x6-vue

DEMO

  1. DAG画布参照x6官方的DAG示例实现

Components

shape 名称描述
Noderect等同于Shape.Rect
Edgeedge等同于Shape.Edge
VueShapevue-shape使用@antv/x6-vue-shape渲染的自定义vue组件的容器,可以将slots.default内容渲染到节点内。
Shape.Rectrect矩形。
Shape.Circlecircle圆形。
Shape.Ellipseellipse椭圆。
Shape.Polygonpolygon多边形。
Shape.Polylinepolyline折线。
Shape.Pathpath路径。
Shape.Imageimage图片。
Shape.HTMLhtmlHTML 节点,使用 foreignObject 渲染 HTML 片段。
Shape.TextBlocktext-block文本节点,使用 foreignObject 渲染文本。
Shape.BorderedImageimage-bordered带边框的图片。
Shape.EmbeddedImageimage-embedded内嵌入矩形的图片。
Shape.InscribedImageimage-inscribed内嵌入椭圆的图片。
Shape.Cylindercylinder圆柱。
Shape.Edgeedge边。
Shape.DoubleEdgedouble-edge双线边。
Shape.ShadowEdgeshadow-edge阴影边。

另外提供帮助函数

名称描述
useCell使用这个函数可以通过传递markup之类的参数自定义节点
useVueShape使用这个函数自定义vue的渲染内容定制更加复杂的节点
useCellEvent通过这个函数绑定事件到cell上面
名称描述
PortGroup提供ports/groups/<group_name>相关的配置,同时也作为Port组件的容器,提供一个默认的group名称
Port调用addPort/removePort操作当前连接桩,比x6官方多提供一个magnet参数(默认情况需要使用attrs/circle/magnet进行配置)。另外,Port也可以独立使用。

使用Port和PortGroup的时候,可以放在一个以port命名的slot里面(考虑到默认的VueShape会将默认的slot认为是用户自定义的节点,这里使用slots.port区分一下),也可以直接使用默认的slot

<Node id="1" :x="100" :y="100" label="node1">
  <PortGroup name="in" position="top" :attrs="{circle: {r: 6, magnet: true, stroke: '#31d0c6'}}">
    <Port id="id1" />
    <Port id="id2" :magnet="false" />
  </PortGroup>
</Node>
<Node id="2" :x="200" :y="200" label="node2">
  <Port id="id1" />
</Node>
名称描述
Grid渲染网格
Background渲染背景
Scroller滚动组件
Clipboard剪贴板,配合Keyboard组件可以使用ctrl+c/ctrl+x/ctrl+v
Keyboard键盘快捷键
MouseWheel鼠标滚轮,支持使用滚轮实现画布放大缩小
Connecting配置连线相关参数和帮助方法
名称描述
Snapline对齐线
Selection点选/框选
MiniMap小地图
Stencil内置的带分组和搜索功能的拖拽组件,还提供StencilGroup以实现分组功能
Contextmenu右键菜单
TeleportContainer一个默认和useVueShape绑定到同一个view的容器,使用这个组件的时候,可以不用手动调用useTeleport,也不用专门指定view

TODO

<x6-node id="44" :x="400" :y="300" label="node4">
  <x6-port-group name="in" position="top" :attrs="{circle: {r: 6, magnet: true, stroke: '#31d0c6'}}">
    <x6-port id="id1" />
  </x6-port-group>
</x6-node>
<Node id="9" :x="500" :y="200" label="node9" :width="300" :height="200">
  <Node id="99" :x="550" :y="220" label="node99" :width="200" :height="150">
    <Node id="999" :x="580" :y="240" label="node999"></Node>
  </Node>
</Node>

默认的x6-vue-shape把每一个节点渲染成一个vue的app导致渲染性能问题 通过vue3提供的Teleport功能,让当前App的子组件渲染到foreignobject内部。解决性能问题的同时,也能更好的处理VueShape组件内部数据及时更新的问题

DEMO

import { defineComponent, reactive } from 'vue'
import Graph, { Node, Edge, Grid, Background } from 'antv-x6-vue'


export default defineComponent({
  setup(props) {
    // ...
    const state = reactive({
      showGrid: true,
      y: 10,
      visible: true,
    })
    const methods = {
      added(e) {
        console.log('added', e)
      },
      click(e) {
        console.log('click', e)
      },
      changed(e) {
        console.log('changed', e)
      },
    }
    return { ...toRefs(state), ...methods }
  },
})

// template
<template>
  <Graph>
    <Node id="1" :x="100" :y="100" />
    <Node id="2" :x="200" :y="200" />
    <Edge id="e1" source="1" target="2" />
    <VueShape primer="rect" id="3" :x="200" :y="300" :width="160" :attrs="{rect: {fill: '#ddd', stroke: '#333'}, label: {text: 'VueShape'}}">
      <div>这里是一个vue的组件</div>
      <img style="width: 30px;height:30px;" src="https://v3.cn.vuejs.org/logo.png" />
    </VueShape>
    <CustomNode
      v-if="visible"
      primer="circle"
      id="4"
      :x="400" :y="y"
      :attrs="{circle: {fill: '#ddd', stroke: '#333'}, label: {text: 'CustomNode'}}"
      @added="added"
      @click="click"
      @cell:change:position="changed"
    >
      <span>Hello</span>
    </CustomNode>
    <Edge id="e2" source="1" target="3" />
    <Background />
    <Grid :visible="showGrid" />
    <Snapline />
    <Selection />
    <Clipboard />
  </Graph>
</template>

image