效果展示
在线预览 源码
SVG介绍
阮一峰: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:视口开始位置 -->
<svg> 的width 属性和height 属性,指定了 SVG 图像在 HTML 元素中所占据的宽度和高度。 <viewBox> 属性的值有四个数字,分别是左上角的横坐标和纵坐标、视口的宽度和高度。
形状标签
常用的基本形状,也是我们常用的标签
属性
通过属性可以去修改SVG的一些样式
.red {
fill: red;
}
.fancy {
fill: none;
stroke: black;
stroke-width: 3px;
}
SVG 的 CSS 属性与网页元素有所不同,主要的属性如下:
fill:填充色
stroke:描边色
stroke-width:边框宽度
Antv/X6介绍
https://x6.antv.vision/zh/
Antv:蚂蚁集团数据可视化团队
简介
X6:基于 HTML 和 SVG 的图编辑引擎,X6 是 AntV 旗下的图编辑引擎,提供了一系列开箱即用的交互组件和简单易用的节点定制能力,方便我们快速搭建 DAG 图、ER 图、流程图等应用。 图编辑核心能力:节点、连线与画布
使用
Step 1 创建容器
在页面中创建一个用于容纳 X6 绘图的容器,可以是一个 div 标签。
<div id="container"></div>
Step 2 准备数据
X6 支持 JSON 格式数据,该对象中需要有节点 nodes 和边 edges 字段,分别用数组表示:
const data = {
nodes: [
{
id: 'node1',
x: 40,
y: 40,
width: 80,
height: 40,
label: 'hello',
},
{
id: 'node2',
x: 160,
y: 180,
width: 80,
height: 40,
label: 'world',
},
],
edges: [
{
source: 'node1',
target: 'node2',
},
],
};
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继承的选项外,还支持以下选项。
属性名 | 类型 | 默认值 | 描述 |
---|
x | Number | 0 | 节点位置 x 坐标,单位为 ‘px’。 | y | Number | 0 | 节点位置 y 坐标,单位为 ‘px’。 | width | Number | 1 | 节点宽度,单位为 ‘px’。 | height | Number | 1 | 节点高度,单位为 ‘px’。 | angle | Number | 0 | 节点旋转角度。 |
添加节点
const rect = graph.addNode({
shape: '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.Rect | rect | rect | 矩形 | Shape.Circle | circle | circle | 圆形 | Shape.Ellipse | ellipse | ellipse | 椭圆 | Shape.Polygon | polygon | polygon | 多边形 | Shape.Path | path | path | 路径 | Shape.Image | image | image | 图片 | Shape.HTML | html | – | HTML 节点,使用 foreignObject 渲染 HTML 片段 | Shape… | … | … | … |
自定义节点
https://x6.antv.vision/zh/docs/tutorial/intermediate/custom-node/#gatsby-focus-wrapper 我们可以通过 markup 和 attrs 来定制节点的形状和样式, markup 可以类比 HTML ,attrs 类比 CSS 。
1、注册
export const GAS_SHAPE_NAME = 'gas-shape'
Graph.registerNode(
GAS_SHAPE_NAME,
{
...customNodeOptions,
},
true
)
配置解析
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,
},
diagramName: {
width: 64,
refX: 32,
refY: '100%',
textAnchor: 'middle',
textVerticalAnchor: 'bottom',
fontSize: 14,
fill: '#009CFF',
},
},
ports: { ...ports },
}
标签结构
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 的属性
边的属性
属性名 | 类型 | 默认值 | 描述 |
---|
source | TerminalData | - | 源节点或起始点。 | target | TerminalData | - | 目标节点或目标点。 | vertices | Point.PointLike[] | - | 路径点。 | router | RouterData | - | 路由。 | connector | ConnectorData | - | 连接器。 | labels | Label[] | - | 标签。 | defaultLabel | Label | 默认标签 | 默认标签。 |
箭头marker样式
节点间连线
链接桩
https://x6.antv.vision/zh/docs/tutorial/basic/port/ 负责连线的输入与输出
添加
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
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,
})
})
2、添加链接桩
node.addPort({
group: 'absolute',
args: {
x: Math.round(x),
y: Math.round(y),
},
silent: false,
})
连线规则
https://x6.antv.vision/zh/docs/tutorial/basic/interacting/#%E8%BF%9E%E7%BA%BF%E8%A7%84%E5%88%99
定制节点和边的交互行为
interacting
interacting: check
? {
nodeMovable: false,
edgeMovable: false,
magnetConnectable: false,
vertexDeletable: false,
}
: true
对连线过程进行控制
connecting
{
connecting: {
router: {
name: 'manhattan',
args: {
padding: 1,
},
},
connector: {
name: 'rounded',
args: {
radius: 8,
},
},
anchor: 'center',
connectionPoint: 'anchor',
allowBlank: false,
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',
},
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 重新设计和实现了渲染架构
图编辑器实现
画布初始化
新建画布
let graph = new Graph(graphOptions())
配置解析
const graphOptions = (check = false) => {
return {
container: document.getElementById('gasDiagramPanel'),
interacting: check
? {
nodeMovable: false,
edgeMovable: false,
magnetConnectable: false,
vertexDeletable: false,
}
: true,
snapline: true,
history: !check,
selecting: {
enabled: true,
multiple: !check,
rubberband: false,
},
grid: {
visible: !check,
size: 20,
type: 'mesh',
args: {
color: '#e9e9e9',
thickness: 2,
},
},
scroller: {
enabled: true,
pageVisible: false,
pageBreak: false,
pannable: true,
},
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、开始拖拽
选项 | 类型 | 说明 |
---|
node | Node | 开始拖拽的节点【添加的节点】 | e | MouseEvent / 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',
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…
graph.on('cell:click', ({ e, x, y, cell }) => { })
事件 | cell 节点/边 | node 节点 | edge 边 | blank 画布空白区域 |
---|
单击 | cell:click | node:click | edge:click | blank:click | 双击 | cell:dblclick | node:dblclick | edge:dblclick | blank:dblclick | 右键 | cell:contextmenu | node:contextmenu | edge:contextmenu | blank:contextmenu | 鼠标按下 | cell:mousedown | node:mousedown | edge:mousedown | blank:mousedown | 移动鼠标 | cell:mousemove | node:mousemove | edge:mousemove | blank:mousemove | 鼠标抬起 | cell:mouseup | node:mouseup | edge:mouseup | blank:mouseup | 鼠标滚轮 | cell:mousewheel | node:mousewheel | edge:mousewheel | blank:mousewheel | 鼠标进入 | cell:mouseenter | node:mouseenter | edge:mouseenter | graph:mouseenter | 鼠标离开 | cell:mouseleave | node:mouseleave | edge:mouseleave | graph:mouseleave |
获取数据
node.getData()
设置数据
node.setData({
...data
})
修改节点属性
node.attr('selector/attr', value)
线的拖拽
graph.on('cell:mouseenter', ({ cell, node }) => {
if (!cell.isNode()) {
cell.addTools([
'vertices',
'segments',
])
}
})
画布销毁
graph.dispose()
查看模式
禁用以下操作,保留点击查看的交互
new Graph({
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,
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
|