【前端】AntV X6学习笔记

使用 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.2
graph.zoomTo(1.2) // 将画布缩放级别设置为 1.2
// 将画布中元素缩小或者放大一定级别,让画布正好容纳所有元素,可以通过 maxScale 配置最大缩放级别
graph.zoomToFit({ maxScale: 1 })
graph.centerContent() // 将画布中元素居中展示

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

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lucky小维

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值