创建可交互的图表: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
);

export default {
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 = new Graph({
        container: container,
        autoResize: true,
        width: 900,
        height: 480,
        translating: {
          useLocalCoordinates: true,
          restrict(cellView) {
            //#region 
            let cell = cellView ? cellView.cell : false;
            if (!cell) return false;
            let yuliu = cell.label
              ? cell.label.includes("点") || cell.label.includes("空")
              : false;
            if (yuliu) {
              return false;
            }
            let temp = cell.label ? cell.label.includes("线") : false;
            if (temp) {
              return false;
            }
           //#endregion
            if (!cellView || !cellView.model) {
              return;
            }
            if (cellView.model.isLink()) {
              // 连线不受限制
              return undefined;
            }
            return cellView.model.getBBox();
          },
        },
        connecting: {
          router: "manhattan",
          connector: {
            name: "normal",
            args: {
              radius: 8,
            },
          },
          anchor: "center",
          connectionPoint: "anchor",
          allowBlank: false,
          snap: {
            radius: 20,
          },
          createEdge() {
            return new Shape.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) {
              return false;
            }
            if (!sourceMagnet) {
              return false;
            }
            if (!targetMagnet) {
              return false;
            }
            return true;
          },
        },
        interacting: function (cellView) {
          if (
            cellView.cell.getData() != undefined &&
            !cellView.cell.getData().disableMove
          ) {
            return { nodeMovable: false };
          }
          return true;
        },
      });
      // 对齐线
      this.graph.use(
        new Snapline({
          enabled: true,
        })
      );
      // 框选
      // this.graph.use(
      //   new Selection({
      //     enabled: true,
      //     showNodeSelectionBox: true,
      //   })
      // );
      this.graph.use(
        new Keyboard({
          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) {
          return false;
        }
        let temp = cell.label ? cell.label.includes("线") : false;
        if (temp) {
          return false;
        }
        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) {
          return false;
        }
        let temp = cell.label ? cell.label.includes("线") : false;
        if (temp) {
          return false;
        }
        this.delCell = cell;
      });
      this.graph.bindKey("delete", () => {
        if (this.delCell) {
          this.graph.removeNode(this.delCell);
        }
        // const cells = this.graph.getSelectedCells();
        return false;
      });

      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); // 更新图形位置
                return false; // 跳出循环,只对第一个目标位置进行吸附
              }
        }
        //#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换成type
      startDragToGraph(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";
export const startDragToGraph = (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); // 更新图形位置
               return false; // 跳出循环,只对第一个目标位置进行吸附
             }
       }
       //#endregion
    })

  const dnd = new Dnd({
    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",
    },
  ],
};

你可能感兴趣的:(交互,前端)