前言:
之前分別做了vue2和vue3項目里的網絡拓撲圖功能,發現對antv X6的講解博客比較少,最近終于得閑碼一篇了!
需求:
用戶可以自己拖拽節點,節點之間可以隨意連線,保存拓撲圖數據后傳給后端,然后在另一個頁面拿到之前的數據進行渲染展示。
最終成品如下圖:
一、準備工作:?
1、裝依賴
npm install --save @antv/x6
2、布局樣式
首先我們先規劃兩塊地方,左邊用來放可以拖的節點,右邊是antv X6的畫布,如下圖:(隨意做的demo,比較丑哈)
布局代碼:
<template><div class="dashboard-container"><p>選擇節點</p><div class="antvBox"><div class="menu-list"><div v-for="item in moduleList" :key="item.id"><img :src="item.image" alt="" /><p>{{ item.name }}</p></div></div><div class="canvas-card"><div id="container" /></div></div></div>
</template><script>
export default {name: "antvX6",data() {return {moduleList: [{id: 1,name: "節點1",image: require("@/assets/img/1.png"),},{id: 8,name: "節點2",image: require("@/assets/img/2.png"),},{id: 2,name: "節點3",image: require("@/assets/img/3.png"),},{id: 3,name: "節點4",image: require("@/assets/img/4.png"),},],};},
};
</script>
<style lang="scss" scoped>
.dashboard-container {.antvBox {display: flex;width: 100%;height: 100%;color: black;padding-top: 20px;.menu-list {height: 100%;width: 300px;padding: 0 10px;box-sizing: border-box;display: flex;justify-content: space-between;align-content: flex-start;flex-wrap: wrap;> div {margin-bottom: 10px;border-radius: 5px;padding: 0 10px;box-sizing: border-box;cursor: pointer;color: black;width: 105px;display: flex;flex-wrap: wrap;justify-content: center;img {height: 50px;width: 50px;}P {width: 90px;text-align: center;}}}.canvas-card {width: 1700px;height: 750px;box-sizing: border-box;> div {width: 1400px;height: 750px;border: 2px dashed #2149ce;}}}
}
</style>
3、添加拖拽事件
我們要先給左側圖標加一個拖拽結束的事件:
代碼如下:
draggable="true"
@dragend="handleDragEnd($event, item)"
在methods定義handleDragEnd函數:?
// 拖動后松開鼠標觸發事件handleDragEnd(e, item) {console.log(e, item); // 可以獲取到最后拖動后松開鼠標時的坐標和拖動的節點相關信息},
效果 :
這個時候我們可以去頁面試著拖動一個左邊的圖標,在鼠標松開時會看到控制臺輸出了節點相關信息,如下圖:
?以上就是準備工作了
二、使用antv X6
1、引入antv X6
import { Graph } from "@antv/x6";
2、初始化畫布
先在data(){}定義graph做畫布示例對象:
定義一個初始化函數,并且在mounted里面調用如下:
initGraph() {const container = document.getElementById("container");this.graph = new Graph({container: container, // 畫布容器width: container.offsetWidth, // 畫布寬height: container.offsetHeight, // 畫布高background: false, // 背景(透明)snapline: true, // 對齊線// 配置連線規則connecting: {snap: true, // 自動吸附allowBlank: false, // 是否允許連接到畫布空白位置的點allowMulti: true, // 是否允許在相同的起始節點和終止之間創建多條邊allowLoop: true, // 是否允許創建循環連線,即邊的起始節點和終止節點為同一節點highlight: true, // 拖動邊時,是否高亮顯示所有可用的節點highlighting: {magnetAdsorbed: {name: "stroke",args: {attrs: {fill: "#5F95FF",stroke: "#5F95FF",},},},},router: {// 對路徑添加額外的點name: "orth",},connector: {// 邊渲染到畫布后的樣式name: "rounded",args: {radius: 8,},},},panning: {enabled: false,},mousewheel: {enabled: true, // 支持滾動放大縮小zoomAtMousePosition: true,modifiers: "ctrl",minScale: 0.5,maxScale: 3,},grid: {type: "dot",size: 20, // 網格大小 10pxvisible: true, // 渲染網格背景args: {color: "#a0a0a0", // 網格線/點顏色thickness: 2, // 網格線寬度/網格點大小},},});},
mounted() {this.initGraph();},
這里就是一些對畫布的配置,多數我都加了注釋,如有部分配置不懂可以評論區問我或者查官方文檔
3、畫布添加節點
代碼如下:
//添加節點到畫布addHandleNode(x, y, id, image, name) {this.graph.addNode({id: id,shape: "image", // 指定使用何種圖形,默認值為 'rect'x: x,y: y,width: 60,height: 60,imageUrl: image,attrs: {body: {stroke: "#ffa940",fill: "#ffd591",},label: {textWrap: {width: 90,text: name,},fill: "black",fontSize: 12,refX: 0.5,refY: "100%",refY2: 4,textAnchor: "middle",textVerticalAnchor: "top",},},ports: {groups: {group1: {position: [30, 30],},},items: [{group: "group1",id: "port1",attrs: {circle: {r: 6,magnet: true,stroke: "#ffffff",strokeWidth: 2,fill: "#5F95FF",},},},],},zIndex: 10,});},
這里使用了antv X6提供的一個方法addNode,傳入的參數分別是:x坐標、y坐標、id節點唯一標識、image圖片、name節點名稱,我的案例這五種就夠了,如果有不同需求可以自己加
4、調用addHandleNode函數
在我們之前寫了的拖動節點結束后的函數(handleDragEnd)里面去調用上面那個函數,代碼如下:
// 拖動后松開鼠標觸發事件handleDragEnd(e, item) {console.log(e, item); // 可以獲取到最后拖動后松開鼠標時的坐標和拖動的節點相關信息this.addHandleNode(e.pageX - 500,e.pageY - 200,new Date().getTime(),item.image,item.name);},
以上所有操作做完應該就可以完成節點拖拽、連線功能:
5、上圖我們可以發現還差一些需求:
-
節點上的那個藍色的連接樁一直顯示,有點遮擋圖標,也不太好看
-
節點無法刪除
-
節點之間的連線也無法刪除
這些都是需要點擊操作的事件,需要了解antv X6的事件系統,官方文檔貼圖如下
需求1:鼠標移入節點再顯示連接樁
定義一個函數nodeAddEvent,代碼如下:
nodeAddEvent() {const { graph } = this;const container = document.getElementById("container");const changePortsVisible = (visible) => {const ports = container.querySelectorAll(".x6-port-body");for (let i = 0, len = ports.length; i < len; i = i + 1) {ports[i].style.visibility = visible ? "visible" : "hidden";}};this.graph.on("node:mouseenter", () => {changePortsVisible(true);});this.graph.on("node:mouseleave", () => {changePortsVisible(false);});},
然后把這個函數在initGraph里面調用一下:
效果如下:
?
需求2:可以選中并刪除節點
想要的效果如下圖:
先在data(){}里面定義curSelectNode,然后在nodeAddEvent函數里加入以下代碼:
// 節點綁定點擊事件this.graph.on("node:click", ({ e, x, y, node, view }) => {console.log("點擊!!!", node);// 判斷是否有選中過節點if (this.curSelectNode) {// 移除選中狀態this.curSelectNode.removeTools();// 判斷兩次選中節點是否相同if (this.curSelectNode !== node) {node.addTools([{name: "boundary",args: {attrs: {fill: "#16B8AA",stroke: "#2F80EB",strokeWidth: 1,fillOpacity: 0.1,},},},{name: "button-remove",args: {x: "100%",y: 0,offset: {x: 0,y: 0,},},},]);this.curSelectNode = node;} else {this.curSelectNode = null;}} else {this.curSelectNode = node;node.addTools([{name: "boundary",args: {attrs: {fill: "#16B8AA",stroke: "#2F80EB",strokeWidth: 1,fillOpacity: 0.1,},},},{name: "button-remove",args: {x: "100%",y: 0,offset: {x: 0,y: 0,},},},]);}});
這里使用了antv X6的工具集,官方文檔貼圖如下:(需求3也使用了工具集)
需求3:可以選中并刪除節點間連線
?想要的效果如下:
在nodeAddEvent函數里加入以下代碼:
// 連線綁定懸浮事件this.graph.on("cell:mouseenter", ({ cell }) => {if (cell.shape == "edge") {cell.addTools([{name: "button-remove",args: {x: "100%",y: 0,offset: {x: 0,y: 0,},},},]);cell.setAttrs({line: {stroke: "#409EFF",},});cell.zIndex = 99; // 保證當前懸停的線在最上層,不會被遮擋}});this.graph.on("cell:mouseleave", ({ cell }) => {if (cell.shape === "edge") {cell.removeTools();cell.setAttrs({line: {stroke: "black",},});cell.zIndex = 1; // 保證未懸停的線在下層,不會遮擋懸停的線}});
6、輸出拓撲圖信息
可以寫一個按鈕來保存拓撲圖信息,這里介紹以下兩個個人感覺常用的函數:
//保存畫布,并提交save() {console.log(this.graph.toJSON(), "graph");console.log(this.graph.getNodes(), "node");},
如上圖所示,點擊保存按鈕后 ,控制臺會輸出:
第一個是整個圖的信息,有節點有連線,可以自己展開看看里面的數據,第二個只有節點數據
四、總結
antv X6 是基于 HTML 和 SVG 的圖編輯引擎,提供低成本的定制能力和開箱即用的內置擴展,方便我們快速搭建 DAG 圖、ER 圖、流程圖、血緣圖等應用。
這里只是介紹了一種基礎拓撲圖的案例,也可以將節點image換成react,做一個編輯節點內文字的功能,就變成流程圖了。
查看官方文檔和示例,也很容易加入其他的功能
antv X6案例鏈接:https://x6.antv.antgroup.com/examples
api文檔:Graph | X6?