demo预览

源码

demo图
Antv-X6图编辑器的应用——流程图实现-1671292460202-36e225b2-b6b6-4e7c-bccc-296ff9decbf6.png

项目效果展示

旧版

antv-map-old.png

UI图

antv-map-ui.png

项目效果

  • 编辑端

antv-edit-view.png

  • 地图端

antv-map-view.jpg

SVG介绍

image.png

阮一峰SVG图像入门
SVGtutorial

因为antv/x6是基于SVG的图编辑器,所以SVG的知识有必要了解下的

简介

  • 可缩放矢量图形【基于图形】

全称:Scalable Vector Graphics

  • 定义基于矢量的图形
  • 基于XML语法
  • 放大缩小不会失真
  • 属于万维网标准
  • 可以插入DOM,通过JavaScript和CSS来操作

语法

<svg width=“200” height=“200” viewBox=“-100 -100 200 200”>            	
  	<polygon points="0,0 80,120 -80,120" fill="#234236" />
    <polygon points="0,-40 60,60 -60,60" fill="#0C5C4C" /> 
    <polygon points="0,-80 40,0 -40,0" fill="#38755B" />
    <rect x="-20" y="120" width="40" height="30" fill="brown" />
</svg>
<!– viewBox:视口开始位置 -->

image.png

<svg>width属性和height属性,指定了 SVG 图像在 HTML 元素中所占据的宽度和高度。
<viewBox>属性的值有四个数字,分别是左上角的横坐标和纵坐标、视口的宽度和高度。

形状标签

常用的基本形状,也是我们常用的标签

  • 矩形
  • 圆形
  • 椭圆
  • 线
  • 折线
  • 多边形
  • 路径

属性

通过属性可以去修改SVG的一些样式

.red {
  fill: red;
}

.fancy {
  fill: none;
  stroke: black;
  stroke-width: 3px;
}

SVGCSS 属性与网页元素有所不同,主要的属性如下:

fill:填充色
stroke:描边色
stroke-width:边框宽度

Antv/X6介绍

https://x6.antv.vision/zh/

Antv:蚂蚁集团数据可视化团队

简介

X6:基于 HTMLSVG的图编辑引擎,X6AntV旗下的图编辑引擎,提供了一系列开箱即用的交互组件和简单易用的节点定制能力,方便我们快速搭建 DAG 图、ER 图、流程图等应用。
图编辑核心能力:节点、连线与画布

使用

Step 1 创建容器

在页面中创建一个用于容纳 X6 绘图的容器,可以是一个 div 标签。

<div id="container"></div>
Step 2 准备数据

X6 支持 JSON 格式数据,该对象中需要有节点 nodes 和边 edges 字段,分别用数组表示:

const data = {
  // 节点
  nodes: [
    {
      id: 'node1', // String,可选,节点的唯一标识
      x: 40,       // Number,必选,节点位置的 x 值
      y: 40,       // Number,必选,节点位置的 y 值
      width: 80,   // Number,可选,节点大小的 width 值
      height: 40,  // Number,可选,节点大小的 height 值
      label: 'hello', // String,节点标签
    },
    {
      id: 'node2', // String,节点的唯一标识
      x: 160,      // Number,必选,节点位置的 x 值
      y: 180,      // Number,必选,节点位置的 y 值
      width: 80,   // Number,可选,节点大小的 width 值
      height: 40,  // Number,可选,节点大小的 height 值
      label: 'world', // String,节点标签
    },
  ],
  // 边
  edges: [
    {
      source: 'node1', // String,必须,起始节点 id
      target: 'node2', // String,必须,目标节点 id
    },
  ],
};
Step 3 渲染画布

首先,我们需要创建一个 Graph 对象,并为其指定一个页面上的绘图容器,通常也会指定画布的大小。

import { Graph } from '@antv/x6';

const graph = new Graph({
  container: document.getElementById('container'),
  width: 800,
  height: 600,
});

// 读取数据
graph.fromJSON(data)

画布 Graph

https://x6.antv.vision/zh/docs/tutorial/basic/graph

图的载体,包含了所有元素、渲染及交互。

新建画布
let graph = new Graph(graphOptions())

基类 Cell

https://x6.antv.vision/zh/docs/tutorial/basic/cell

图形共同的基类 ,定义了节点和的边共同属性和方法

                                 ┌──────────────────┐
                             ┌──▶│ Shape.Rect       │
                             │   └──────────────────┘
                             │   ┌──────────────────┐
                             ├──▶│ Shape.Circle     │
                 ┌────────┐  │   └──────────────────┘
              ┌─▶│  Node  │──┤   ┌──────────────────┐
              │  └────────┘  ├──▶│ Shape.Ellipse    │
              │              │   └──────────────────┘
              │              │   ┌──────────────────┐
              │              └──▶│ Shape.Xxx...     │
  ┌────────┐  │                  └──────────────────┘
  │  Cell  │──┤                                      
  └────────┘  │                  ┌──────────────────┐
              │              ┌──▶│ Shape.Edge       │
              │              │   └──────────────────┘
              │  ┌────────┐  │   ┌──────────────────┐
              └─▶│  Edge  │──┼──▶│ Shape.DoubleEdge │
                 └────────┘  │   └──────────────────┘
                             │   ┌──────────────────┐
                             └──▶│ Shape.ShadowEdge │
                                 └──────────────────┘

节点 Node

https://x6.antv.vision/zh/docs/tutorial/basic/node

根据不同的SVG元素来渲染节点和边,x6提供了内置节点和自定义节点

节点属性

节点都有共同的基类 Cell,除了Cell继承的选项外,还支持以下选项。

属性名类型默认值描述
xNumber0节点位置 x 坐标,单位为 ‘px’。
yNumber0节点位置 y 坐标,单位为 ‘px’。
widthNumber1节点宽度,单位为 ‘px’。
heightNumber1节点高度,单位为 ‘px’。
angleNumber0节点旋转角度。
添加节点
const rect = graph.addNode({
  shape: 'rect', // 指定使用何种图形,默认值为 'rect'
  x: 100,
  y: 200,
  width: 80,
  height: 40,
  angle: 30,
  attrs: {
    body: {
      fill: 'blue',
    },
    label: {
      text: 'Hello',
      fill: 'white',
    },
  },
})
内置节点

https://x6.antv.vision/zh/examples/gallery/#category-%E5%86%85%E7%BD%AE%E8%8A%82%E7%82%B9

内置节点与svg标签

构造函数shape 名称svg 标签描述
Shape.Rectrectrect矩形
Shape.Circlecirclecircle圆形
Shape.Ellipseellipseellipse椭圆
Shape.Polygonpolygonpolygon多边形
Shape.Pathpathpath路径
Shape.Imageimageimage图片
Shape.HTMLhtmlHTML 节点,使用 foreignObject 渲染 HTML 片段
Shape…

image.png

自定义节点

https://x6.antv.vision/zh/docs/tutorial/intermediate/custom-node/#gatsby-focus-wrapper
我们可以通过 markup 和 attrs 来定制节点的形状和样式,
markup 可以类比 HTMLattrs 类比 CSS

1、注册

// 自定义节点的名称
export const GAS_SHAPE_NAME = 'gas-shape'

// 对象节点
Graph.registerNode(
    GAS_SHAPE_NAME,
    {
        ...customNodeOptions,
    },
    true // 重名时是否覆盖
)

配置解析

// 自定义对象节点的配置,需要展示更多的节点内容在这里去添加,并更新数据
// https://x6.antv.vision/zh/docs/tutorial/intermediate/custom-node
export const customNodeOptions = {
    // 来指定继承的基类
    inherit: 'rect',
    width: 64,
    height: 105,
    // 标签及选择器
    markup: [
        {
            tagName: 'rect',
            selector: 'body',
        },
        {
            tagName: 'image',
            selector: 'image',
        },
        {
            tagName: 'text', // 标签名称
            selector: 'diagramName', // 选择器
        },
    ],
    // 属性设置
    attrs: {
        body: {
            stroke: 'transparent',
            fill: 'transparent',
        },
        image: {
            width: 64,
            height: 64,
            refX: 0,
            y: 10, // 向下偏移 10px
        },
        diagramName: {
            width: 64,
            refX: 32,
            refY: '100%', // 右下角
            textAnchor: 'middle',
            textVerticalAnchor: 'bottom',
            fontSize: 14,
            fill: '#009CFF',
        },
    },
    // 链接桩配置
    ports: { ...ports },
}

标签结构
gas-shape-mark.png

2、使用
const newNode = graph.createNode({
    shape: GAS_SHAPE_NAME,
   	attrs: {},
    data: {},
})
修改节点
  • node.attr(path, value),详细使用见 attr
// 修改节点属性
node.attr('selector/attr', value)
// 修改携带数据
node.setData({ ...data })
// 获取携带数据
node.getData()

边Edge

https://x6.antv.antgroup.com/tutorial/basic/edge
内置节点节点和边都有共同的基类 Cell, 并继承Cell 的属性

边的属性
属性名类型默认值描述
sourceTerminalData-源节点或起始点。
targetTerminalData-目标节点或目标点。
verticesPoint.PointLike[]-路径点。
routerRouterData-路由。
connectorConnectorData-连接器。
labelsLabel[]-标签。
defaultLabelLabel默认标签默认标签。
箭头marker样式

image.png

节点间连线

链接桩

https://x6.antv.vision/zh/docs/tutorial/basic/port/
负责连线的输入与输出
image.png

添加
graph.addNode({
    x: 60,
    y: 60,
    width: 160,
    height: 80,
    label: 'Rect With Ports',
    ports: [
        { id: 'port1' },
        { id: 'port2' },
        { id: 'port3' },
    ],
})

// 分组添加
graph.addNode({
    x: 60,
    y: 60,
    width: 160,
    height: 80,
    label: 'Rect With Ports',
    groups: {
        top: {
            // 定义连接柱的位置,如果不配置,将显示为默认样式
            position: 'top',
            // 定义连接柱的样式
            attrs: {
                circle: {
                    ...portStyle,
                },
            },
        },
    }, 
  	// 链接桩组定义
    items: [
        {
            group: 'top',
        },
        {
            group: 'right',
        },
        {
            group: 'bottom',
        },
        {
            group: 'left',
        },
    ],  // 链接桩
})
动态添加链接桩

通过鼠标位置,以及当前节点位置,计算链接桩位置

1、计算链接桩位置

// 双击添加链接桩
graph.on('node:dblclick', e => {
    const { e: event, node } = e
    // 当前选中元素
    const $select = document.querySelector('.x6-node-selected > rect')
    if (!$select) {
        return
    }
    // 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
    const position = $select.getBoundingClientRect && $select.getBoundingClientRect()
    if (!position) {
        return
    }

    // 鼠标位置
    const pageX = event.pageX
    const pageY = event.pageY
    // graph.zoom() 缩放层级
    const zoom = graph.zoom()
    // 相对节点左上角的位置/鼠标的位置-元素的位置就是相对位置
    const x = (pageX - position.x) / zoom
    const y = (pageY - position.y) / zoom
    node.addPort({
        group: 'absolute',
        args: {
            // 传递给自定义连接算法的参数
            // 通过鼠标位置,以及当前节点位置,计算链接桩位置
            x: Math.round(x),
            y: Math.round(y),
        },
        silent: false, // 为 true 时不触发 'change:ports' 事件和画布重绘。
    })
})

2、添加链接桩

// 鼠标位置 - 元素位置 = 鼠标相对于节点的位置
node.addPort({
    group: 'absolute',
    args: {
        // 传递给自定义连接算法的参数
        // 通过鼠标位置,以及当前节点位置,计算链接桩位置
        x: Math.round(x),
        y: Math.round(y),
    },
    silent: false, // 为 true 时不触发 'change:ports' 事件和画布重绘。
})
连线规则

https://x6.antv.vision/zh/docs/tutorial/basic/interacting/#%E8%BF%9E%E7%BA%BF%E8%A7%84%E5%88%99

定制节点和边的交互行为

interacting

// 定制节点和边的交互行为 ==> boolean 节点或边是否可交互
interacting: check
? {
      nodeMovable: false,
      edgeMovable: false,
      magnetConnectable: false, // 是否触发连线交互
      vertexDeletable: false, // 边的路径点是否可以被删除
  }
: true
对连线过程进行控制

connecting

 // 连线规则
{
    connecting: {
        // 路由类型
        router: {
            // 连线类型在此修改
            // 曼哈顿路由 'manhattan' 路由是正交路由 'orth' 的智能版本,该路由由水平或垂直的正交线段组成,并自动避开路径上的其他节点(障碍)。
            name: 'manhattan',
                args: {
                padding: 1,
            },
        },
        // 圆角连接器,将起点、路由点、终点通过直线按顺序连接,并在线段连接处通过圆弧连接(倒圆角)。
        connector: {
            name: 'rounded',
                args: {
                radius: 8,
            },
        },
        anchor: 'center',
        connectionPoint: 'anchor',
        // 是否允许连接到画布空白位置的点,默认为 true。
        allowBlank: false,
        // 距离节点或者连接桩 50px 时会触发自动吸附
        snap: {
            radius: 20,
        },
        // 拽出新的边
        createEdge() {
            return new Shape.Edge({
                markup: [
                    {
                        tagName: 'path',
                        selector: 'stroke',
                    },
                    {
                        tagName: 'path',
                        selector: 'fill',
                    },
                ],
                connector: { name: 'rounded' },
                attrs: {
                    fill: {
                        class: 'pipe-ant-line',
                        fill: 'none',
                        connection: true,
                        strokeDasharray: 25,
                        strokeWidth: 3,
                        strokeLinecap: 'round',
                        style: {
                            animation: 'ant-line 30s infinite linear',
                        },
                        // 渐变使用教程:https://x6.antv.vision/zh/docs/api/registry/attr#fill
                        // ??无法垂直渐变
                        /**
                         * 当y1和y2相等,而x1和x2不同时,可创建水平渐变
                         * 当x1和x2相等,而y1和y2不同时,可创建垂直渐变
                         * 当x1和x2不同,且y1和y2不同时,可创建角形渐变
                         */
                        // attrs: { x1: 0, y1: 0, x2: 0, y2: 1, },
                        stroke: '#fff',
                    },
                    stroke: {
                        fill: 'none',
                        connection: true,
                        strokeWidth: 6,
                        strokeLinecap: 'round',
                        stroke: '#8CF7C3',
                    },
                },
                zIndex: 0,
            })
        },
        validateConnection({ targetMagnet }) {
            return !!targetMagnet
        },
    },
}
指定触发某种交互时的高亮样式

highlighting

  • ‘default’ 默认高亮选项,当以下几种高亮配置缺省时被使用。
  • ‘embedding’ 拖动节点进行嵌入操作过程中,节点可以被嵌入时被使用。
  • ‘nodeAvailable’ 连线过程中,节点可以被链接时被使用。
  • ‘magnetAvailable’ 连线过程中,链接桩可以被链接时被使用。
  • ‘magnetAdsorbed’ 连线过程中,自动吸附到链接桩时被使用。
{
    // 高亮样式
    highlighting: {
        // 连线过程中,自动吸附到链接桩时被使用。
        magnetAdsorbed: {
            name: 'stroke',
                args: {
                attrs: {
                    width: 12,
                        r: 6,
                        magnet: true,
                        stroke: '#008CFF',
                        strokeWidth: 2,
                        fill: '#0F67FF',
                },
            },
        },
    },
}

关于2.0

https://x6.antv.antgroup.com/tutorial/about
2.0 重新设计和实现了渲染架构
image.png

图编辑器实现

画布初始化

新建画布
let graph = new Graph(graphOptions())
配置解析

/**
 * @desc 初始化面板配置
 * @param check 查看模式
 */
const graphOptions = (check = false) => {
    return {
        container: document.getElementById('gasDiagramPanel'),
        // 定制节点和边的交互行为 ==> boolean 节点或边是否可交互
        interacting: check
            ? {
                nodeMovable: false,
                edgeMovable: false,
                magnetConnectable: false,
                vertexDeletable: false,
            }
            : true,
        // 对齐线
        snapline: true,
        // 撤销/重做
        history: !check,
        // 点选/框选
        selecting: {
            enabled: true,
            multiple: !check, // 多选【开启】
            rubberband: false, // 启用框选【关闭】
        },
        // 显示网格 // 'dot' | 'fixedDot' | 'mesh'
        grid: {
            visible: !check,
            size: 20, // 网格大小
            type: 'mesh',
            args: {
                color: '#e9e9e9',
                thickness: 2, // 网格线宽度/网格点大小
            },
        },
        // 滚动
        scroller: {
            enabled: true,
            pageVisible: false, // 是否分页
            pageBreak: false,
            pannable: true, // 是否平移
        },
        // 滚轮缩放 MouseWheel
        mousewheel: {
            enabled: true,
            zoomAtMousePosition: true,
            modifiers: ['ctrl', 'meta'],
            maxScale: 3,
            minScale: 0.3,
        },
        resizing: false, // 不能修改大小
        rotating: false, // 不能旋转
        keyboard: !check, // 按键操作
        clipboard: !check, // 剪切板
        autoResize: true,
        onToolItemCreated({ tool }) {
            const options = tool.options
            if (options && options.index % 2 === 1) {
                tool.setAttrs({ fill: 'red' })
            }
        },
        // 连线规则
        connecting: {
            ...
        },
        // 连线高亮
        highlighting: {
           ...
        },
    }
}

添加节点

通过拖拽交互往画布中添加节点

添加拖拽

https://x6.antv.vision/zh/docs/tutorial/basic/dnd

1、初始化
import { Addon } from '@antv/x6'

const dnd = new Addon.Dnd(options)
2、开始拖拽
选项类型说明
nodeNode开始拖拽的节点【添加的节点】
eMouseEvent / JQuery.MouseDownEvent鼠标事件
dnd.start(newNode, e)
<template>
<div
    class="flow-library-item"
    v-for="group in item.groups"
    :key="group.id"
>
    <div
        class="flow-library-item__img"
        :class="'flow-library-item__img--' + group.shape"
        :data-name="group.name"
        :data-id="group.id"
        :data-image="group.image"
        :data-shape="group.shape"
        :style="{
            backgroundImage: `url(${group.image})`,
        }"
        @mousedown.stop="handleonAddNode"
    >
        <div class="flow-library-item__name">{{ group.name }}</div>
    </div>
</div>
</template>
<script>
export default {
    methods: {
        /**
         * 拖拽并添加节点
         * @param e
         */
        addNode(e) {
            const target = e && e.target.closest('.thumbnail-img') // 匹配特定选择器且离当前元素最近的祖先元素
            if (target) {
                const id = target.getAttribute('data-id')
                const label = target.getAttribute('data-label')
                const image = target.getAttribute('data-image')
                const newNode = graph.createNode({
                    shape: 'custom-image',
                    label,
                    attrs: {
                        image: {
                            'xlink:href': `${image}`,
                        },
                    },
                    data: {
                        label,
                        id
                    }
                })
                dnd.start(newNode, e)
            }
        }
    }
}
</script>
添加节点
先创建后添加
创建并添加到画布
const rect = graph.addNode({
    shape: 'rect', // 指定使用何种图形,默认值为 'rect'
    x: 100,
    y: 200,
    width: 80,
    height: 40,
    angle: 30,
    attrs: {
        body: {
            fill: 'blue',
        },
        label: {
            text: 'Hello',
            fill: 'white',
        },
    },
})

推荐第二种,可以通过shape来指定图形类型,包括自定义类型
定制样式

气路图切换

https://x6.antv.vision/zh/docs/tutorial/intermediate/serialization
完成数据的保存和读取

保存
graph.toJSON()
读取
graph.fromJSON()

数据与交互

事件系统

https://x6.antv.vision/zh/docs/tutorial/intermediate/events
回调参数包含鼠标位置x、y,事件对象e…

// cell:元素类型,
// click:事件类型,
graph.on('cell:click', ({ e, x, y, cell }) => { })
事件cell 节点/边node 节点edge 边blank 画布空白区域
单击cell:clicknode:clickedge:clickblank:click
双击cell:dblclicknode:dblclickedge:dblclickblank:dblclick
右键cell:contextmenunode:contextmenuedge:contextmenublank:contextmenu
鼠标按下cell:mousedownnode:mousedownedge:mousedownblank:mousedown
移动鼠标cell:mousemovenode:mousemoveedge:mousemoveblank:mousemove
鼠标抬起cell:mouseupnode:mouseupedge:mouseupblank:mouseup
鼠标滚轮cell:mousewheelnode:mousewheeledge:mousewheelblank:mousewheel
鼠标进入cell:mouseenternode:mouseenteredge:mouseentergraph:mouseenter
鼠标离开cell:mouseleavenode:mouseleaveedge:mouseleavegraph:mouseleave
获取数据
node.getData()
设置数据
node.setData({
    ...data
})
修改节点属性
// selector:选择器
// attr:属性
// value:修改值
node.attr('selector/attr', value)
线的拖拽
//  https://x6.antv.vision/zh/docs/tutorial/intermediate/tools
// 1、vertices 路径点工具,在路径点位置渲染一个小圆点,
// 拖动小圆点修改路径点位置,双击小圆点删除路径点,在边上单击添加路径点。
// 2、segments 线段工具。在边的每条线段的中心渲染一个工具条,可以拖动工具条调整线段两端的路径点的位置。

// 基类 Cell
graph.on('cell:mouseenter', ({ cell, node }) => {
    if (!cell.isNode()) {
        cell.addTools([
            'vertices',
            'segments',
            // {
            //     name: 'button-remove',
            //     args: {
            //         x: '30%',
            //         y: '50%',
            //     },
            // },
        ])
    }
})
画布销毁
graph.dispose()
查看模式

禁用以下操作,保留点击查看的交互

new Graph({
    // 定制节点和边的交互行为 ==> boolean 节点或边是否可交互
    interacting: !check,
    // 撤销/重做
    history: !check,
    // 点选/框选
    selecting: {
        enabled: true,
        multiple: !check, // 多选【开启】
        rubberband: false, // 启用框选【关闭】
    },
    keyboard: !check, // 按键操作
    clipboard: !check, // 剪切板
})
画布缩放及居中

监听页面resize,动态修改画布大小,并居中画布
记得移除监听

mounted(){
  window.removeEventListener('resize', this.autoResize, false)  
},
methods: {
  // 容器大小适配浏览器缩放/防抖一下
  autoResize: debounce(() => {
    const gasContainer = document.querySelector('.gas-diagram-container')
    if (gasContainer && graph) {
      // 画布适配 https://x6.antv.vision/zh/docs/api/graph/transform/#resize
      graph.resize(gasContainer.clientWidth, gasContainer.clientHeight)
    }
  }, 300),
}

画布居中

// 画布居中
graph.centerContent()
节点缩放
缩放设置
resizing: {
    enabled: true,
    minWidth: 64, // 最小宽
    maxWidth: 64 * 2, // 最大宽
    minHeight: 105 / 2, // 最小高
    maxHeight: 105 * 2, // 最大高
    orthogonal: true, // 是否显示中间调整点,默认为 true
    restricted: false, // 调整大小边界是否可以超出画布边缘
    preserveAspectRatio: true, // 调整大小过程中是否保持节点的宽高比例
  }
节点内容改变

https://x6.antv.vision/zh/docs/tutorial/intermediate/attrs
通过相当大小和位置来替换原有单位,达到节点缩放,内容跟着改变
常用参数:

diagramName: {
    // width: 64,
    // refX: 32,
    refWidth: transformToPercent(64, 64),
    refX: transformToPercent(32, 64),
},


右键菜单

扩展

撤销/重做
滚动/缩放
对齐线
快捷键

SVG动画


Thanks

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐