0


创建可交互的图表:AntV X6实现预留空白位置、拖拽吸附与信息修改弹框

使用AntV X6

首先用AntV X6官网的一句简介了解一下什么是X6
X6 是基于 HTML 和 SVG 的图编辑引擎,提供低成本的定制能力和开箱即用的内置扩展,方便我们快速搭建 DAG 图、ER 图、流程图、血缘图等应用。

知道了X6是什么,那么我们就要开始使用了

首先得确定框架,其次就要安装X6
由于项目是vue2的,所以选择的框架为vue2,当然自己也在vue3中写了一版,如有需要vue3的请私信

1.首先在项目中新建一个xxx.vue

<template><div class="container1" style="width=100%; height=100%"><div id="container"></div></div><div v-for="(item, index) in draList":key="index"class="btn" @mousedown="startDrag(item, $event)"><div class="box">{{ item }}</div></div></template>

2.js部分为

前提安装 npm install @antv/x6 @antv/x6-plugin-snapline @antv/x6-plugin-keyboard

<script>import{ Graph, Shape }from"@antv/x6";import{ Snapline }from"@antv/x6-plugin-snapline";import{ Keyboard }from"@antv/x6-plugin-keyboard";import{ startDragToGraph }from"../../utils/drags";
Graph.registerNode("lane",{inherit:"rect",markup:[{tagName:"rect",selector:"body",},{tagName:"rect",selector:"name-rect",},{tagName:"text",selector:"name-text",},],attrs:{body:{fill:"#F8F8F8",stroke:"#f8f8f8",strokeWidth:0.5,},"name-rect":{width:100,height:30,fill:"#f8f8f8",stroke:"#f8f8f8",strokeWidth:0.5,// x: -1,},"name-text":{ref:"name-rect",refY:30,refX:85,textAnchor:"middle",// fontWeight: "bold",fill:"#000",fontSize:17,},},},true);
Graph.registerNode("lane-rect",{inherit:"rect",width:100,height:60,attrs:{body:{strokeWidth:1,stroke:"#5F95FF",fill:"#EFF4FF",},text:{fontSize:12,fill:"#262626",},},},true);
Graph.registerNode("lane-polygon",{inherit:"polygon",width:80,height:80,attrs:{body:{strokeWidth:1,stroke:"#5F95FF",fill:"#EFF4FF",refPoints:"0,10 10,0 20,10 10,20",},text:{fontSize:12,fill:"#262626",},},},true);
Graph.registerEdge("lane-edge",{inherit:"edge",attrs:{line:{stroke:"#A2B1C3",strokeWidth:2,},},label:{attrs:{label:{fill:"#A2B1C3",fontSize:12,},},},},true);exportdefault{data(){return{delCell:{},json:"",graph:{},celll:"",draList:["xxxx艺","xxxx运"],ruleForm:{name:"",count:""},dataPlacer:[{id:"1",shape:"lane",width:170,height:680,pointerEvents:"none",position:{x:15,y:0,},label:"主线程",},{id:"2",shape:"lane",width:170,height:680,position:{x:230,y:0,},data:{disableMove:false,},label:"并行路线",},{id:"3",shape:"lane",width:170,height:680,position:{x:450,y:0,},data:{disableMove:false,},label:"并行路线",},{id:"4",shape:"lane",width:170,height:680,position:{x:670,y:0,},data:{disableMove:false,},label:"并行路线",},{id:"4.5",shape:"lane",width:170,height:680,position:{x:890,y:0,},data:{disableMove:false,},label:"并行路线",},// 开始预留空位{id:"5",shape:"rect",width:140,height:42,position:{x:30,y:90,},data:{disableMove:false,},label:"起点",fontSize:20,attrs:{magnet:false,body:{stroke:"#d9d9d9",magnet:false,nodeMovable:false,strokeDasharray:"5,5",},},parent:"1",},{id:"6",shape:"rect",width:140,height:42,position:{x:30,y:190,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"1",},{id:"7",shape:"rect",width:140,height:42,position:{x:30,y:290,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"1",},{id:"8",shape:"rect",width:140,height:42,position:{x:30,y:390,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"1",},// 预留结束// 第二列预留{id:"10",shape:"rect",width:140,height:42,position:{x:245,y:90,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"2",},{id:"11",shape:"rect",width:140,height:42,position:{x:245,y:190,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"2",},{id:"12",shape:"rect",width:140,height:42,position:{x:245,y:290,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"2",},{id:"13",shape:"rect",width:140,height:42,position:{x:245,y:390,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"2",},// 第二列预留结束// 第三列预留开始{id:"15",shape:"rect",width:140,height:42,position:{x:465,y:90,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"3",},{id:"16",shape:"rect",width:140,height:42,position:{x:465,y:190,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"3",},{id:"17",shape:"rect",width:140,height:42,position:{x:465,y:290,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"3",},{id:"18",shape:"rect",width:140,height:42,position:{x:465,y:390,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"3",},// 点三列预留结束// 第四列预留开始{id:"20",shape:"rect",width:140,height:42,position:{x:685,y:90,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"4",},{id:"21",shape:"rect",width:140,height:42,position:{x:685,y:190,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"4",},{id:"22",shape:"rect",width:140,height:42,position:{x:685,y:290,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"4",},{id:"23",shape:"rect",width:140,height:42,position:{x:685,y:390,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"4",},// 第四列预留结束// 第五列预留开始{id:"25",shape:"rect",width:140,height:42,position:{x:905,y:90,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"5",},{id:"26",shape:"rect",width:140,height:42,position:{x:905,y:190,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"5",},{id:"27",shape:"rect",width:140,height:42,position:{x:905,y:290,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"5",},{id:"28",shape:"rect",width:140,height:42,position:{x:905,y:390,},data:{disableMove:false,},label:"空位",attrs:{body:{stroke:"#d9d9d9",strokeDasharray:"5,5",},},parent:"5",},],};},methods:{init(){let container = document.getElementById("container");this.graph =newGraph({container: container,autoResize:true,width:900,height:480,translating:{useLocalCoordinates:true,restrict(cellView){//#region let cell = cellView ? cellView.cell :false;if(!cell)returnfalse;let yuliu = cell.label
              ? cell.label.includes("点")|| cell.label.includes("空"):false;if(yuliu){returnfalse;}let temp = cell.label ? cell.label.includes("线"):false;if(temp){returnfalse;}//#endregionif(!cellView ||!cellView.model){return;}if(cellView.model.isLink()){// 连线不受限制returnundefined;}return cellView.model.getBBox();},},connecting:{router:"manhattan",connector:{name:"normal",args:{radius:8,},},anchor:"center",connectionPoint:"anchor",allowBlank:false,snap:{radius:20,},createEdge(){returnnewShape.Edge({connector:"normal",attrs:{line:{stroke:"#2d8cf0",strokeWidth:1,targetMarker:{name:"classic",size:8,},},},router:{name:"orth",},// zIndex: 0,});},validateConnection({
            sourceView,
            targetView,
            sourceMagnet,
            targetMagnet,}){if(sourceView === targetView){returnfalse;}if(!sourceMagnet){returnfalse;}if(!targetMagnet){returnfalse;}returntrue;},},interacting:function(cellView){if(
            cellView.cell.getData()!=undefined&&!cellView.cell.getData().disableMove
          ){return{nodeMovable:false};}returntrue;},});// 对齐线this.graph.use(newSnapline({enabled:true,}));// 框选// this.graph.use(//   new Selection({//     enabled: true,//     showNodeSelectionBox: true,//   })// );this.graph.use(newKeyboard({enabled:true,global:true,}));this.graph.on("cell:mouseenter",({ cell })=>{let temp = cell.label ? cell.label.includes("线"):false;if(temp){return{selectable:false,};}let yuliu = cell.label
          ? cell.label.includes("点")|| cell.label.includes("空"):false;if(yuliu){return{selectable:false,};}if(cell.isNode()){let ports = container.querySelectorAll(".x6-port-body");let show =true;for(let i =0, len = ports.length; i < len; i = i +1){
            ports[i].style.visibility = show ?"visible":"hidden";}
          cell.addTools([{name:"button-remove",args:{x:0,y:0,offset:{x:20,y:10},},},]);}else{
          cell.addTools([{name:"vertices",args:{stopPropagation:false,},},{name:"segments"},//添加线上的平移{name:"button",args:{markup:[{tagName:"circle",selector:"button",attrs:{r:16,stroke:"#3d85f2","stroke-width":3,fill:"#3d85f2",cursor:"pointer",},},{tagName:"text",textContent:"🗑︎",selector:"icon",attrs:{fill:"#fff","font-size":25,"text-anchor":"middle","pointer-events":"none",y:"0.3em",},},],distance:40,onClick({ view }){const node = view.cell;
                  node.remove();},},},]);}});this.graph.on("cell:mouseleave",({ cell })=>{
        cell.removeTools();let ports = container.querySelectorAll(".x6-port-body");let show =false;for(let i =0, len = ports.length; i < len; i = i +1){
          ports[i].style.visibility = show ?"visible":"hidden";}});this.graph.on("node:click",({ cell })=>{let yuliu = cell.label
          ? cell.label.includes("点")|| cell.label.includes("空"):false;if(yuliu){returnfalse;}let temp = cell.label ? cell.label.includes("线"):false;if(temp){returnfalse;}this.delCell = cell;let textClick = cell.store.data.attrs.label;if(textClick.text.includes("xxxx艺")){this.flag1 =true;}else{this.flag =true;}this.celll = cell;if(cell.form){this.ruleForm.name = cell.form.name;this.ruleForm.count = cell.form.count;}});this.graph.on("cell:mousedown",({ cell })=>{let yuliu = cell.label
          ? cell.label.includes("点")|| cell.label.includes("空"):false;if(yuliu){returnfalse;}let temp = cell.label ? cell.label.includes("线"):false;if(temp){returnfalse;}this.delCell = cell;});this.graph.bindKey("delete",()=>{if(this.delCell){this.graph.removeNode(this.delCell);}// const cells = this.graph.getSelectedCells();returnfalse;});this.graph.on("node:mousemove",({ cell })=>{//#region 过滤let temp = cell.label ? cell.label.includes("线"):false;if(temp){return{selectable:false,};}let yuliu = cell.label
          ? cell.label.includes("点")|| cell.label.includes("空"):false;if(yuliu){return{selectable:false,};}//#endregion//#region 吸附const snapThreshold =100;const snapSegmentLength =1;let x1 =30,x2 =245,x3 =465,x4 =685,x5 =905;let y1 =90,y2 =190,y3 =290,y4 =390;const targetPoints =[{x: x1,y: y1 },{x: x1,y: y2 },{x: x1,y: y3 },{x: x1,y: y4 },// { x: x1, y: y5 },{x: x2,y: y1 },{x: x2,y: y2 },{x: x2,y: y3 },{x: x2,y: y4 },// { x: x2, y: y5 },{x: x3,y: y1 },{x: x3,y: y2 },{x: x3,y: y3 },{x: x3,y: y4 },// { x: x3, y: y5 },{x: x4,y: y1 },{x: x4,y: y2 },{x: x4,y: y3 },{x: x4,y: y4 },// { x: x4, y: y5 },{x: x5,y: y1 },{x: x5,y: y2 },{x: x5,y: y3 },{x: x5,y: y4 },// { x: x5, y: y5 },];let node = cell.position();for(let i =0; i < targetPoints.length; i++){const point = targetPoints[i];if(node.x <= point.x &&node.x <= point.y &&
            node.y > point.x &&node.y <= point.y){
                cell.position(point.x, point.y);}const distX = Math.abs(point.x - node.x);const distY = Math.abs(point.y - node.y);// 如果距离小于吸附区域,则进行吸附if(distX < snapThreshold && distY < snapThreshold){// 计算吸附点,使其对齐到网格const snapX =
                  Math.round(point.x / snapSegmentLength)* snapSegmentLength;const snapY =
                  Math.round(point.y / snapSegmentLength)* snapSegmentLength;
                cell.position(snapX, snapY, cell);// 更新图形位置returnfalse;// 跳出循环,只对第一个目标位置进行吸附}}//#endregion});},reservePlace(){this.graph.fromJSON(this.dataPlacer);// let cells = [];// this.dataPlacer.forEach((item) => {//   if (item.shape === "lane-edge") {//     cells.push(this.graph.createEdge(item));//   } else {//     cells.push(this.graph.createNode(item));//   }//   this.graph.resetCells(cells);//   this.graph.zoomToFit({ padding: 10, maxScale: 1 });// });},// 拖startDrag(type, e){if(type.includes("xxx艺")){
        type ="艺名称";}else{
        type ="运名称";}let temp =this.graph;let arr =[];let nam ="";let tt = temp.container.innerText.split("\n");if(tt){for(let j =0; j < tt.length; j++){const dd = tt[j];if(dd !=""){
            arr.push(dd.trim());}}for(let i =0; i < arr.length +1; i++){const name = arr[i];
          nam =`${type}${arr.length -25+1}`;// console.log(nam);if(name == type){
            nam =`${type}${i +1}`;
            arr.push(nam);}}}// 以上代码可以根据自己的实际情况来写,也可以不写以上代码// 如果不写上面的代码,把下面括号中的nam换成typestartDragToGraph(this.graph, nam, e);},},
mounted(){this.init();this.reservePlace();},}</script>

3.在utils文件夹中新建一个drags.js来实现拖拽

安装npm i @antv/x6-plugin-dnd

import{ Dnd }from"@antv/x6-plugin-dnd";exportconststartDragToGraph=(graph, type, e)=>{const node = graph.createNode({shape:"rect",width:140,height:42,attrs:{label:{text: type,fill:"#00539a",textAnchor:"middle",verticalAnchor:"middle",fontSize:20,ellipsis:true,breakWord:true,textWrap:{width:-10,height:-10,ellipsis:true,},},body:{stroke:"#00539a",strokeWidth:2,fill:"#ffffff",},},tools:[// 使拖拽的元素具有删除的图标{name:"button-remove",args:{x:20,y:10,},},],ports: ports,});
  graph.on('node:added',({cell})=>{const snapThreshold =100;const snapSegmentLength =1;let x1 =30,x2 =245,x3 =465,x4 =685,x5 =905;let y1 =90,y2 =190,y3 =290,y4 =390;const targetPoints =[{x: x1,y: y1 },{x: x1,y: y2 },{x: x1,y: y3 },{x: x1,y: y4 },{x: x2,y: y1 },{x: x2,y: y2 },{x: x2,y: y3 },{x: x2,y: y4 },{x: x3,y: y1 },{x: x3,y: y2 },{x: x3,y: y3 },{x: x3,y: y4 },{x: x4,y: y1 },{x: x4,y: y2 },{x: x4,y: y3 },{x: x4,y: y4 },{x: x5,y: y1 },{x: x5,y: y2 },{x: x5,y: y3 },{x: x5,y: y4 },];let node = cell.position();for(let i =0; i < targetPoints.length; i++){const point = targetPoints[i];if(node.x <= point.x &&node.x <= point.y &&
             node.y > point.x &&node.y <= point.y){
               cell.position(point.x, point.y);}const distX = Math.abs(point.x - node.x);const distY = Math.abs(point.y - node.y);// 如果距离小于吸附区域,则进行吸附if(distX < snapThreshold && distY < snapThreshold){// 计算吸附点,使其对齐到网格const snapX =
                 Math.round(point.x / snapSegmentLength)* snapSegmentLength;const snapY =
                 Math.round(point.y / snapSegmentLength)* snapSegmentLength;
               cell.position(snapX, snapY, cell);// 更新图形位置returnfalse;// 跳出循环,只对第一个目标位置进行吸附}}//#endregion})const dnd =newDnd({target: graph,});
  dnd.start(node, e);};const ports ={groups:{top:{position:"top",zIndex:1,attrs:{portBody:{"port-type":"ellipse",r:8,magnet:true,stroke:"#2D8CF0",strokeWidth:2,fill:"#2D8CF0",},portCross:{"port-type":"decorator",ref:"portBody","ref-x":-4.5,"ref-y":0,"ref-dx":0,"ref-dy":0,position:{name:"center",},d:"M 0 0 L 10 0 M 5 -5 L 5 5",stroke:"#fff",strokeWidth:1,fill:"#fff",magnet:true,zIndex:2,},},markup:[{tagName:"circle",selector:"portBody",},{tagName:"path",selector:"portCross",},],},bottom:{position:"bottom",zIndex:1,attrs:{portBody:{"port-type":"ellipse",r:8,magnet:true,stroke:"#2D8CF0",strokeWidth:2,fill:"#2D8CF0",},portCross:{"port-type":"decorator",ref:"portBody","ref-x":-4.5,"ref-y":0,"ref-dx":0,"ref-dy":0,position:{name:"center",},d:"M 0 0 L 10 0 M 5 -5 L 5 5",stroke:"#fff",strokeWidth:1,fill:"#fff",magnet:true,zIndex:2,},},markup:[{tagName:"circle",selector:"portBody",},{tagName:"path",selector:"portCross",},],},left:{position:"left",zIndex:1,attrs:{portBody:{"port-type":"ellipse",r:8,magnet:true,stroke:"#2D8CF0",strokeWidth:2,fill:"#2D8CF0",},portCross:{"port-type":"decorator",ref:"portBody","ref-x":-4.5,"ref-y":0,"ref-dx":0,"ref-dy":0,position:{name:"center",},d:"M 0 0 L 10 0 M 5 -5 L 5 5",stroke:"#fff",strokeWidth:1,fill:"#fff",magnet:true,zIndex:2,},},markup:[{tagName:"circle",selector:"portBody",},{tagName:"path",selector:"portCross",},],},right:{position:"right",zIndex:1,attrs:{portBody:{"port-type":"ellipse",r:8,magnet:true,stroke:"#2D8CF0",strokeWidth:2,fill:"#2D8CF0",},portCross:{"port-type":"decorator",ref:"portBody","ref-x":-4.5,"ref-y":0,"ref-dx":0,"ref-dy":0,position:{name:"center",},d:"M 0 0 L 10 0 M 5 -5 L 5 5",stroke:"#fff",strokeWidth:1,fill:"#fff",magnet:true,zIndex:2,},},markup:[{tagName:"circle",selector:"portBody",},{tagName:"path",selector:"portCross",},],},},items:[{id:"port1",group:"top",},{id:"port2",group:"bottom",},{id:"port3",group:"left",},{id:"port4",group:"right",},],};
标签: 交互 前端

本文转载自: https://blog.csdn.net/weixin_44637104/article/details/131665180
版权归原作者 木得是 所有, 如有侵权,请联系我们删除。

“创建可交互的图表:AntV X6实现预留空白位置、拖拽吸附与信息修改弹框”的评论:

还没有评论