【前端】AntV X6学习笔记

Lucky小维 2024-06-12 16:33:02 阅读 55

使用 AntV X6 版本 2.x,下面官网地址

Vue 节点 | X6 (antgroup.com)

path 路径节点,通过 svg 的 d 值来显示 svg

svg 在线编辑器_svg 矢量图在线制作工具-易点在线矢量图形编辑器 (wxeditor.com)

使用场景 | X6 (antgroup.com)

拖动一个图标去看它的源码里,复制 d 里面的值,放入 path 节点里的 path 属性值里面

<path id="svg_1" d="m199.50248,160.86426l0.19807,-0.63855l0.80193,-0.28418l0.80193,0.28418l0.19807,0.63855l-0.55497,0.51208l-0.89007,0l-0.55497,-0.51208z" stroke-width="1.5" stroke="#000" fill="#fff"/>

path 节点

{ id: "node3", shape: "path", x: 180, y: 150, width: 40, height: 40, label: "svg图", path: "m193,160.31151l22.53615,0l6.96384,-19.86229l6.96385,19.86229l22.53615,0l-18.2321,12.27543l6.96421,19.86229l-18.23211,-12.27576l-18.2321,12.27576l6.96421,-19.86229l-18.2321,-12.27543", attrs: { body: { fill: "#efdbff", stroke: "#9254de", }, }, },

核心

引入的插件

import { Graph, Shape } from '@antv/x6' // 导入 @antv/x6 库中的 Graph 和 Shape 类import { Stencil } from '@antv/x6-plugin-stencil' // 导入拖拽式元素选择插件import { Transform } from '@antv/x6-plugin-transform' // 导入变换操作插件import { Selection } from '@antv/x6-plugin-selection' // 导入选元素插件import { Snapline } from '@antv/x6-plugin-snapline' // 导入图形对齐插件import { Keyboard } from '@antv/x6-plugin-keyboard' // 导入键盘事件处理插件import { Clipboard } from '@antv/x6-plugin-clipboard' // 导入复制和粘贴图形元素插件import insertCss from 'insert-css' // 导入动态插入 CSS 函数import { History } from '@antv/x6-plugin-history' // 导入图形编辑历史记录插件

第 1 步,绘制画布

initGraph() { // 创建一个 Graph 实例,参数是一个配置对象 this.graph = new Graph({ container: document.getElementById('container'), // 画布容器 background: false, // 背景(透明) autoResize: true, //自动更新大小 // 网格配置 grid: { type: 'mesh', // 网格类型为网点(点阵)形式 size: 10, // 网格大小 10px visible: true, // 渲染网格背景 args: { color: '#eeeeee', // 网格线/点颜色 thickness: 2, // 网格线宽度/网格点大小 }, }, mousewheel: { enabled: true, // 启用鼠标滚轮缩放功能 zoomAtMousePosition: true, // 缩放中心点在鼠标位置 modifiers: 'ctrl', // 按住 Ctrl 键进行缩放 minScale: 0.5, // 最小缩放比例 maxScale: 3, // 最大缩放比例 }, panning: { enabled: true, // 允许平移功能 modifiers: 'alt', // 按住 Alt 键进行平移 }, // 配置连线规则 connecting: { router: 'manhattan', // 连线的默认路由器为曼哈顿算法 connector: { name: 'rounded', // 连接器形状为圆角 args: { radius: 8, // 圆角半径 }, }, anchor: 'center', // 连线默认锚点为中心点 connectionPoint: 'anchor', // 连线默认连接点为锚点 allowBlank: false, // 不允许空连接 snap: { radius: 20, // 连接吸附的半径范围 }, createEdge() { // 创建连线时返回一个新的 Edge 对象 return new Shape.Edge({ attrs: { line: { stroke: '#A2B1C3', // 连线颜色 strokeWidth: 2, // 连线宽度 targetMarker: { name: 'block', // 目标箭头形状为方块 width: 12, // 箭头宽度 height: 8, // 箭头高度 }, }, }, zIndex: 0, // 层级设置为 0 }); }, validateConnection({ targetMagnet }) { // 验证连接是否有效,目标连接点必须存在 return !!targetMagnet; }, }, highlighting: { magnetAdsorbed: { name: 'stroke', // 线条描边样式为高亮 args: { attrs: { fill: '#5F95FF', // 线条填充颜色 stroke: '#5F95FF', // 线条描边颜色 }, }, }, }, }); // 使用插件 this.graph .use( new Transform({ // 平移、缩放、旋转等变换操作 resizing: true, // 允许调整大小 rotating: true, // 允许旋转 }) ) .use( new Selection({ // 选择和操纵图形。提供了选择区域、单选等功能 rubberband: true, // 启用选择框 showNodeSelectionBox: true, // 显示节点的选择框 }) ) .use(new Snapline()) // 可用于对齐图形元素 .use(new Keyboard()) // 允许通过键盘快捷键来控制图形编辑 .use(new Clipboard()) // 提供了对图形的复制和粘贴操作 .use(new History()); // 历史记录撤销使用 this.graph.centerContent(); // 画布内容居中显示},

第 2 步,初始化连接桩

//初始化默认连接桩 initPorts() { this.ports = { groups: { top: { position: 'top', attrs: { circle: { r: 4, magnet: true, stroke: '#5F95FF', strokeWidth: 1, fill: '#fff', style: { visibility: 'hidden', }, }, }, }, right: { position: 'right', attrs: { circle: { r: 4, magnet: true, stroke: '#5F95FF', strokeWidth: 1, fill: '#fff', style: { visibility: 'hidden', }, }, }, }, bottom: { position: 'bottom', attrs: { circle: { r: 4, magnet: true, stroke: '#5F95FF', strokeWidth: 1, fill: '#fff', style: { visibility: 'hidden', }, }, }, }, left: { position: 'left', attrs: { circle: { r: 4, magnet: true, stroke: '#5F95FF', strokeWidth: 1, fill: '#fff', style: { visibility: 'hidden', }, }, }, }, }, items: [ { group: 'top', }, { group: 'right', }, { group: 'bottom', }, { group: 'left', }, ], } },

第 3 步,注册自定义节点

enrollNode() { // 注册自定义模板节点 'custom-image' Graph.registerNode( 'custom-image', { width: 62, // 节点宽度 height: 62, // 节点高度 markup: [ { tagName: 'rect', selector: 'body', // 节点外部矩形 }, { tagName: 'image', }, { tagName: 'text', selector: 'label', }, ], attrs: { body: { stroke: 'green', // 外部矩形边框颜色 fill: 'pink', // 外部矩形填充颜色 }, image: { refWidth: '100%', // 图片宽度和节点宽度一致 refHeight: '100%', // 图片高度和节点高度一致 }, label: { refX: 20, refY: 82, textAnchor: 'center', textVerticalAnchor: 'bottom', fontSize: 16, fill: '#fff', // 文字颜色 }, }, ports: { ...this.ports }, // 节点的连接点配置 }, true // 是否强制覆盖同名节点类型 );},

第四步, 初始化侧边栏

initStencil() { // 创建一个 Stencil 实例 this.stencil = new Stencil({ title: '接线图', // Stencil 标题 target: this.graph, // 目标 Graph 实例 stencilGraphWidth: 200, // Stencil 绘图区宽度 stencilGraphHeight: 0, // Stencil 绘图区高度(0 表示自适应高度) collapsable: true, // 是否可折叠 groups: [ // 定义分组 // { // title: "基础流程图", // name: "group1", // graphHeight: 50, // }, // 系统设计图分组 { title: '系统设计图', name: 'group2', graphHeight: 0, // 分组内部绘图区高度(0 表示自适应高度) layoutOptions: { rowHeight: 70, // 分组内部行高 }, }, ], layoutOptions: { columns: 2, // 列数 columnWidth: 80, // 列宽度 rowHeight: 55, // 行高度 }, }); // 将 Stencil 实例的容器添加到指定的 DOM 元素中 document.getElementById('stencil').appendChild(this.stencil.container); // 插入样式 this.insertCss();},

第 4 步,绘制左侧栏

initStencil() { // 创建一个 Stencil 实例 this.stencil = new Stencil({ title: '接线图', // Stencil 标题 target: this.graph, // 目标 Graph 实例 stencilGraphWidth: 200, // Stencil 绘图区宽度 stencilGraphHeight: 0, // Stencil 绘图区高度(0 表示自适应高度) collapsable: true, // 是否可折叠 groups: [ // 定义分组 // { // title: "基础流程图", // name: "group1", // graphHeight: 50, // }, // 系统设计图分组 { title: '系统设计图', name: 'group2', graphHeight: 0, // 分组内部绘图区高度(0 表示自适应高度) layoutOptions: { rowHeight: 70, // 分组内部行高 }, }, ], layoutOptions: { columns: 2, // 列数 columnWidth: 80, // 列宽度 rowHeight: 55, // 行高度 }, }); // 将 Stencil 实例的容器添加到指定的 DOM 元素中 document.getElementById('stencil').appendChild(this.stencil.container); // 插入样式,修改了左侧栏底色 this.insertCss();},

第 5 步,添加监听事件

initEvent() { // 当鼠标进入节点时,显示连接点 this.graph.on('node:mouseenter', () => { const container = document.getElementById('graph-container'); const ports = container.querySelectorAll('.x6-port-body'); this.showPorts(ports, true); }); // 当鼠标离开节点时,隐藏连接点 this.graph.on('node:mouseleave', () => { const container = document.getElementById('graph-container'); const ports = container.querySelectorAll('.x6-port-body'); this.showPorts(ports, false); }); // 节点点击事件 this.graph.on('cell:click', ({ cell }) => { // 清除之前选中节点的样式 this.curCel ? this.curCel.attr('body/stroke', null) : null; this.curCel ? this.curCel.attr('line/stroke', '#c0c0c0') : '#c0c0c0'; // 设置当前选中节点的样式为红色 this.curCel = cell; this.curCel.attr('body/stroke', 'red'); this.curCel.attr('line/stroke', 'red'); // 获取节点的文字标签(可能在text/text中,也可能在label/text中) this.formData.nodeName = cell.getAttrs()?.text?.text ? cell.getAttrs()?.text?.text : cell.getAttrs()?.label?.text ? cell.getAttrs()?.label?.text : null; // 获取节点的名称 this.formData.Name = cell.getAttrs()?.data?.Name; }); // 快捷键与事件 this.graph.bindKey(['meta+c', 'ctrl+c'], () => { // 复制选中的单元格 const cells = this.graph.getSelectedCells(); if (cells.length) { this.graph.copy(cells); } return false; }); this.graph.bindKey(['meta+x', 'ctrl+x'], () => { // 剪切选中的单元格 const cells = this.graph.getSelectedCells(); if (cells.length) { this.graph.cut(cells); } return false; }); this.graph.bindKey(['meta+v', 'ctrl+v'], () => { // 粘贴剪贴板中的内容 if (!this.graph.isClipboardEmpty()) { const cells = this.graph.paste({ offset: 32 }); this.graph.cleanSelection(); this.graph.select(cells); } return false; }); this.graph.bindKey('delete', () => { // 删除选中的单元格 const cells = this.graph.getSelectedCells(); if (cells.length) { this.graph.removeCells(cells); } }); this.graph.bindKey(['ctrl+1', 'meta+1'], () => { // 放大画布 const zoom = this.graph.zoom(); if (zoom < 1.5) { this.graph.zoom(0.1); } }); this.graph.bindKey(['ctrl+2', 'meta+2'], () => { // 缩小画布 const zoom = this.graph.zoom(); if (zoom > 0.5) { this.graph.zoom(-0.1); } }); this.graph.bindKey(['meta+z', 'ctrl+z'], () => { // 撤销操作 if (this.graph.canUndo()) { this.graph.undo(); } return false; }); this.graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => { // 重做操作 if (this.graph.canRedo()) { this.graph.redo(); } return false; }); this.graph.bindKey(['meta+a', 'ctrl+a'], () => { // 全选 const nodes = this.graph.getNodes(); if (nodes) { this.graph.select(nodes); } });},

第六步,加载数据

load(){ this.$http({ url: this.$http.adornUrl('/topoImg/loader/queryImgList'), method: 'post', data: this.$http.adornData({ }), }).then(({ data }) => { if (data && data.code === 0) { this.imgList = data.list const encounteredKeys = { } //相同名称的设备只取其一 for (let index = 0; index < this.imgList.length; index++) { const item = this.imgList[index] if (!encounteredKeys[item.imgKey]) { encounteredKeys[item.imgKey] = true this.imageNodes.push( this.graph.createNode({ //调用自定义节点 shape: 'custom-image', label: item.imgKey, attrs: { data: { deviceName: '', filePositionOptions: '下居中', valueLamp: '0', }, image: { 'xlink:href': item.imgContent.startsWith('img') ? this.$http.adornUrl('/') + item.imgContent : item.imgContent, }, }, }) ) } } this.imageNodes.push( this.graph.createNode({ shape: 'custom-text', attrs: { data: { deviceName: '', filePositionOptions: '中居中', valueLamp: '0', }, image: { 'xlink:href': '', }, }, }) ) // console.log("左侧的数据是", this.imageNodes); this.stencil.load(this.imageNodes, 'group2') } else { this.imgList = [] } this.load(this.receiveId) })}//获取画布数据 load(id) { if (!id) { return } this.$http({ url: this.$http.adornUrl('/topo/loader/getInfo'), method: 'post', data: this.$http.adornData({ id: id, }), }).then(({ data }) => { if (data && data.code === 0) { if (data.data.x6json === undefined || data.data.x6json === '') { return } this.graph.fromJSON(JSON.parse(data.data.x6json)) // let topojson = data.data.topojson; // let x6json = data.data.x6json; // console.log("---------------------X--"); // console.log("topojson值", JSON.parse(JSON.parse(topojson))); // console.log("-------------X----------"); // console.log("x6json值", JSON.parse(x6json)); //下面先遍历页面的图,然后再去遍历连接线 // nodeS.nodeDataArray.map((item) => { // const [x, y] = item.loc.split(" ").map((value) => parseInt(value)); // // console.log("x和y的值是", x, y); // this.graph.addNode({ // shape: "custom-image", // label: item.imgKey, // id: item.key, // x: x, // y: y, // width: item.width, // height: item.height, // data: { // size: item.size, // loc: item.loc, // imgKey: item.imgKey, // valContent: item.valContent, // }, // attrs: { // image: { // "xlink:href": item.source.startsWith("img") // ? this.$http.adornUrl("/") + item.source // : item.source, // }, // }, // }); // }); //添加连接线 // nodeS.linkDataArray.map((item) => // this.graph.addEdge({ // source: { cell: item.from }, // target: { cell: item.to }, // attrs: { // line: { // stroke: "#c0c0c0", // strokeWidth: 2, // targetMarker: { // name: "block", // width: 12, // height: 8, // }, // }, // }, // zIndex: 0, // }) // ); } }) },

补充

//修改画布内置的样式,正常css无法修改 insertCss() { return insertCss(` #container { display: flex; border: 1px solid #dfe3e8; } #stencil { width: 180px; height: 100%; position: relative; border-right: 1px solid #dfe3e8; } #graph-container { width: calc(100% - 180px); height: 100%; } .x6-widget-stencil { background-color: #fff; } .x6-widget-stencil-title { background-color: #fff; } .x6-widget-stencil-group-title { background-color: #fff !important; } .x6-widget-transform { margin: -1px 0 0 -1px; padding: 0px; border: 1px solid #239edd; } .x6-widget-transform > div { border: 1px solid #239edd; } .x6-widget-transform > div:hover { background-color: #3dafe4; } .x6-widget-transform-active-handle { background-color: #3dafe4; } .x6-widget-transform-resize { border-radius: 0; } .x6-widget-selection-inner { border: 1px solid #239edd; } .x6-widget-selection-box { opacity: 0; } `) }, props: ['id'], mounted() { this.receiveId = this.$props.id this.initGraph() //初始化画布 this.initPorts() //初始化连接桩 this.enrollNode() //注册自定义节点 this.initStencil() //初始化左侧栏ui this.initEvent() //添加监听事件 this.queryApiList() //查找接口列表 this.loadData() //加载数据 }, <!-- 下面是节点属性框 --> <div v-if="drawer" class="node_attr_box"> <div style="font-size: 20px; padding: 10px 5px; background-color: slategray"> <span>组态属性</span> <span style="float: right" class="close_icon"><i class="el-icon-close" @click="drawer = false"></i></span> </div> <el-form :model="nodeForm"> <h3>节点设置</h3> <el-form-item label="设备ID" prop="idNum"> <el-input v-model="nodeForm.idNum" size="mini" style="width: 100px"></el-input> </el-form-item> <hr /> <h3>文本设置</h3> <el-form-item label="文本" prop="nodeName"> <el-input v-model="nodeForm.nodeName" @change="onNameChange" size="mini" style="width: 100px"></el-input> </el-form-item> </el-form> <hr /> </div>

遇到问题

gojs和x6的图形显示不符合预期

在 X6 中,网格是渲染/移动节点的最小单位,默认是 10px ,也就是说位置为 { x: 24, y: 38 } 的节点渲染到画布后的实际位置为 { x: 20, y: 40 }

// 构建画布 myDiagram = $( sx.Diagram, 'myDiagramp', // 必须指定或引用 HTML 元素 { // 是否显示网格 grid: $( sx.Panel, 'Grid', $(sx.Shape, 'LineH', { stroke: 'lightgray', strokeWidth: 0.5 }), // 水平辅助线,浅灰色 $(sx.Shape, 'LineH', { stroke: 'gray', strokeWidth: 0.5, interval: 10, }), // 水平主要线,灰色,每隔10个单位绘制一条 $(sx.Shape, 'LineV', { stroke: 'lightgray', strokeWidth: 0.5 }), // 垂直辅助线,浅灰色 $(sx.Shape, 'LineV', { stroke: 'gray', strokeWidth: 0.5, interval: 10, }) // 垂直主要线,灰色,每隔10个单位绘制一条 ), // 只读模式 isReadOnly: false, // 初始化画布缩放比例 scale: 0.7, allowDrop: true, // 允许从工具栏拖拽节点到画布 allowZoom: true, // 允许缩放 'draggingTool.dragsLink': true, // 拖拽节点时是否同时移动连线 'draggingTool.isGridSnapEnabled': true, // 拖拽节点时是否吸附到网格 'linkingTool.isUnconnectedLinkValid': true, // 允许创建未连接的连线 'linkingTool.portGravity': 20, // 连线吸附到节点端口的力度 'relinkingTool.isUnconnectedLinkValid': true, // 允许重新连接未连接的连线 'relinkingTool.portGravity': 20, // 重新连接连线时吸附到节点端口的力度 'relinkingTool.fromHandleArchetype': $(sx.Shape, 'Diamond', { segmentIndex: 0, cursor: 'pointer', desiredSize: new sx.Size(8, 8), fill: 'tomato', stroke: 'darkred', }), // 重新连接连线时起始点的手柄样式 'relinkingTool.toHandleArchetype': $(sx.Shape, 'Diamond', { segmentIndex: -1, cursor: 'pointer', desiredSize: new sx.Size(8, 8), fill: 'darkred', stroke: 'tomato', }), // 重新连接连线时结束点的手柄样式 'linkReshapingTool.handleArchetype': $(sx.Shape, 'Diamond', { desiredSize: new sx.Size(7, 7), fill: 'lightblue', stroke: 'deepskyblue', }), // 调整连线形状时的手柄样式 rotatingTool: $(TopRotatingTool), // 自定义旋转工具 'rotatingTool.snapAngleMultiple': 15, // 旋转时对齐到的角度倍数 'rotatingTool.snapAngleEpsilon': 15, // 角度对齐的容差范围 'undoManager.isEnabled': true, // 启用撤销/重做功能 } )

同步gojs中渲染的表格,也是10px,现在考虑解决x6缩放问题

graph.resize(800, 600) // resize 画布大小graph.translate(20, 20) // 在 x、y 方向上平移画布graph.zoom(0.2) // 将画布缩放级别增加 0.2(默认为1)graph.zoom(-0.2) // 将画布缩放级别减少 0.2graph.zoomTo(1.2) // 将画布缩放级别设置为 1.2// 将画布中元素缩小或者放大一定级别,让画布正好容纳所有元素,可以通过 maxScale 配置最大缩放级别graph.zoomToFit({ maxScale: 1 })graph.centerContent() // 将画布中元素居中展示

加入了zoom后,差不多比例了

接着研究线的坐标,发现是base64图片有偏移,保存时候添加了,解决了这个问题。又发现了新的问题,还是线的问题,图片与图片直连,两边都可以,就是旋转之后,连接线不匹配。研究线



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。