• antv x6连线与取消连线的操作+自定义连接桩+节点选择/框选


    antv x6连线与取消连线的操作+自定义连接桩+节点选择/框选

    我们的需求:给连接桩赋不同样式与数据代表不同类型,连线中只有相同类型的连接桩才可以相连,并且左边连接桩为输入,右边连接桩为输出,输入必须和输出相连,输出也只能和输入相连,每次选择节点后回传数据

    在这里插入图片描述
    假设我们的数据结构如下;

    {
    			"name": "python",
    			"icon": "images/pythonScripts@2x.png",
    			"icon1": "images1/pythonScripts@2x.png",
    			"description": "",
    			"category": "Scripts",
    			"typeID": 3,
    			"inputs": [
    				{
    					"name": "Data",
    					"type": "DataType"
    				},
    				{
    					"name": "Model",
    					"type": "ModelType"
    				}
    			],
    			"outputs": [
    				{
    					"name": "Data",
    					"type": "DataType"
    				},
    				{
    					"name": "Model",
    					"type": "ModelType"
    				}
    			]
    		},
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    然后我们先来看:

    1.自定义连接桩

    1.1定义连接桩位置及数据

    let groups = {},
        items = [],
        inputs = type.inputs,
        outputs = type.outputs,
        name = type.name;
    
      if (inputs.length) {
        inputs.forEach((item, index) => {
          // 如果type为data类型
          if (item.type === "dataType") {
            groups.input1 = {
              position: {
                name: "absolute", // 连接桩固定属性
              },
              attrs: {
                fo: {
                  magnet: "true", 
                },
                data: item, // 自定义与与节点/边关联的业务数据
              },
            };
            items.push({
              id: `${name}_input_${item.name}_${item.type}`, // 使用拼接字符串代表此连接桩属性
              group: "input1",
              args: { // 连接桩位置
                x: 0,
                y: 47,
                angle: 45,
              },
            });
          }
          // 如果type为model类型
          if (item.type === "") {
            groups.input2 = {
              position: {
                name: "absolute",
              },
              attrs: {
                fo: {
                  magnet: "true",
                },
                data: item, // 自定义与与节点/边关联的业务数据,为我们上方展示的数据结构
              },
            };
            items.push({
              id: `${name}_input_${item.name}_${item.type}`,
              group: "input2",
              args: {
                x: 0,
                y: 104,
                angle: 45,
              },
            });
          }
        });
      }
    
      if (outputs.length) {
        outputs.forEach((item, index) => {
          // 如果type为data类型
          if (item.type === "") {
            // console.log(123123123123123123123123123123123123123123);
            groups.output1 = {
              position: {
                name: "absolute",
              },
              attrs: {
                fo: {
                  magnet: "true",
                },
                data: item, // 自定义与与节点/边关联的业务数据
              },
            };
            items.push({
              id: `${name}_output_${item.name}_${item.type}`,
              group: "output1",
              args: {
                x: 99,
                y: 47,
                angle: 45,
              },
            });
          }
    
          // 如果type为model类型
          if (item.type === model") {
            groups.output2 = {
              position: {
                name: "absolute",
              },
              attrs: {
                fo: {
                  magnet: "true",
                },
                data: item, // 自定义与与节点/边关联的业务数据
              },
            };
            items.push({
              id: `${name}_output_${item.name}_${item.type}`,
              group: "output2",
              args: {
                x: 99,
                y: 104,
                angle: 45,
              },
            });
          }
        });
      }
    
      const ports = {// 写入port 
        groups: groups, 
        items: items,
      };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    1.2连接桩样式

    然后来看我们的自定义连接桩,这里使用的react组件

    import React from "react";
    import { Tooltip } from 'antd';
    
    import { insertCss } from "insert-css";
    
    import nodeWrap from '@/assets/images/nodeWrap@2x.png'
    
    const imgBaseUrl = "http://192.168.19.107:800/";
    
    export const CustomizeNode = ({ type }) => {
      return (
        <div className="AINodeWrap">
          <Tooltip title={type.name}>
            <div className="AINodeTitle">{type.name}</div>
          </Tooltip>
          <div className="AINodeContent">
            <img className="AIIcon" src={`${imgBaseUrl}${type.icon1}`} alt=''></img>
          </div>
        </div>
      );
    };
    
    insertCss(`
    .AINodeWrap {
      width: 100px;
      height: 130px;
      background-image: url(${nodeWrap});
      background-size: 100px 130px;
    }
    
    .AINodeTitle {
      width: 100%;
      height: 24px;
      border-radius: 8px 8px 0px 0px;
      font-size: 12px;
      font-family: PingFangSC-Semibold, PingFang SC;
      font-weight: 600;
      color: #F9FDFF;
      line-height: 24px;
      text-align: center;
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }
    .AINodeContent {
      width: 100%;
      height: 102px;
      border-radius: 0 0 8px 8px;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    .AIIcon {
      width: 98px;
      height: 112px;
      padding-top: 25px;
    }
    .text {
      font-size: 14px;
      margin-top: 12px;
      font-family: Helvetica;
      font-weight: ;
      font-size: 14px;
      color: #040D26;
      letter-spacing: 0;
      text-align: center;
      line-height: 12px;
    }
    `);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    1.3初始化连接桩

    然后初始化拖拽画布,节点与连接桩(自定义节点我们之前写过了)

    const target = graph.createNode({
        width: 100,
        height: 130,
        shape: "react-shape",
        component: <CustomizeNode type={type} />,
        event: "node:dblclick",
        data: type,
        portMarkup: [Markup.getForeignObjectMarkup()], // 链接桩的 DOM 结构,当 ports.groups 和 ports.items 都没有为对应的链接桩指定 markup 时,则使用这个默认选项来渲染链接桩
        ports: ports,
      });
    
      dnd.start(target, e.nativeEvent);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里一定不要忘了在生成画布的时候把自定义连接桩引入

     const graph = new Graph({
          container: container,
          width: "100%",
          height: "100%",
          background: {
            color: "#383838", opacity: "0.5"
           },
          onPortRendered(args) { // 自定义连接桩
            const selectors = args.contentSelectors
            const container = selectors && selectors.foContent
            const portAttr = args.port.attrs.data
            if (container) {
              const root = ReactDOM.createRoot(container);
              root.render(
                <Tooltip title={portAttr.type}>
                  <CustomizePort portAttr={portAttr}></CustomizePort> {/* eslint-disable-next-line */}
                </Tooltip>,
              );
            }
          }
       })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    然后我们来看连线,连线的时候呢,我们不能规定哪条线连不上,只有让它连上以后再取消操作

    2.连接、取消连接边的操作

     graph.on(
       "edge:connected",
       ({ e, isNew, edge, previousCell, currentCell }) => {
         const source = edge.getSourceCell();
         // 删除之后也会调用这个方法,source为空
         if (!source) {
           return;
         }
         let json = graph.toJSON();
         console.log("连接边的操作", json, json.cells);
         // 判断是否连接的目标节点的输入点
         if (edge.shape === "dag-edge") {
           const edgeSource = edge.source.port
           const edgeTarget = edge.target.port
           const edgeInput = allTrim(edgeSource.split('_')[3]) // 从字符串中把类型取出来
           const edgeOutput = allTrim(edgeTarget.split('_')[3])
           if (
             // 判断是否是输出节点和输入节点相连
             edgeSource.indexOf("output") === -1 ||
             edgeTarget.indexOf("input") === -1
             || edgeInput !== edgeOutput // 或者input和output类型不相同
           ) {
             graph.removeEdge(edge.id); // 取消连接边的操作
           }
         }
         setGraphJSON(json);
       }
     );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    记得连线规则要在初始化画布时配置哦~

     const graph = new Graph({
          container: container,
          width: "100%",
          height: "100%",
          background: {
            color: "#383838", opacity: "0.5"
    
          },
     	connecting: {
            // 配置全局的连线规则
            snap: true, // 是否自动吸附
            allowMulti: true, // 是否允许在相同的起始节点和终止之间创建多条边
            allowNode: false, // 是否允许边链接到节点(非节点上的链接桩)
            allowBlank: false, // 是否允许连接到空白点
            allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,
            allowEdge: false, // 是否允许边链接到另一个边
            highlight: true, // 拖动边时,是否高亮显示所有可用的连接桩或节点
            connector: "algo-connector", // 边渲染到画布后的样式
            connectionPoint: "anchor", // 指定连接点
            anchor: "center", // 指定被连接的节点的锚点
            // validateMagnet({ e, magnet, view, cell }) {
            //   // magnet 被按下时,是否创建新的边
            //   console.log("magent", e, magnet, view, cell);
            //   return false;
            // },
            createEdge() {
              // 连接的过程中创建新的边
              return graph.createEdge({
                shape: "dag-edge",
                attrs: {
                  line: {
                    strokeDasharray: "5 5",
                  },
                },
                zIndex: -1,
              });
            },
          },
      })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    还有连线样式

     // 连接过程中产生的新边的样式
        Graph.registerEdge(
          "dag-edge",
          {
            inherit: "edge",
            attrs: {
              line: {
                stroke: "#9DADB6",
                strokeWidth: 2,
                sourceMarker: null,
                targetMarker: {
                  //
                  name: "block", // 实心箭头
                },
              },
            },
          },
          true
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3. 节点或者边被选择/框选

    3.1被选中时回传选中数据

    graph.on("cell:selected", ({ cell, options }) => {
      const allSelected = graph.getSelectedCells();
      console.log('allSelected============', allSelected)
      setSelectJSON(allSelected);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.2 节点/边被取消选中时触发。

    // 画布选择数据重新变为画布全部cell

    graph.on("cell:unselected", ({ cell, options }) => {
      let json = graph.toJSON();
      console.log("取消选中", json);
      setSelectJSON();
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意,使用框选也要在画布初始化时配置哦

    const graph = new Graph({
          container: container,
          width: "100%",
          height: "100%",
          background: {
            color: "#383838", opacity: "0.5"
    
          },
           selecting: { // 配置框选
            enabled: true,
            className: 'my-selecting', // 附加样式名,用于定制样式,
            multiple: true, // 是否启用点击多选,默认为 true。启用多选后按住 ctrl 或 command 键点击节点实现多选。
            rubberband: false,  // 是否启用框选
            rubberNode: true,  // 自定义框选节点
            rubberEdge: true,  // 自定义框选边
            movable: true,  // 在多选情况下,选中的节点是否一起移动
            following: true, // 在多选情况下,选中的节点是否跟随鼠标实时移动
            showNodeSelectionBox: true, // 是否显示节点的选择框
            showEdgeSelectionBox: true, // 是否显示边的选择框
            strict: true, // 启用框选时,选框完全包围节点时才选中节点,否则只需要选框与节点的包围盒(BBox)相交即可选中节点
            content: (selection) => {
              return StringExt.template(
                '<%= length %> node<%= length > 1 ? "s":"" %> selected.',
              )({ length: selection.length })
            }
          },
        });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    最后,怕大家看的懵,我再贴一个完整的画布初始化配置上来吧~

     //官方文档写的是componentDidMount,因为react取消了三个生命周期函数,所以使用useEffect
      useEffect(() => {
        // 连接过程中产生的新边的样式
        Graph.registerEdge(
          "dag-edge",
          {
            inherit: "edge",
            attrs: {
              line: {
                stroke: "#9DADB6",
                strokeWidth: 2,
                sourceMarker: null,
                targetMarker: {
                  //
                  name: "block", // 实心箭头
                },
              },
            },
          },
          true
        );
    
        // 自定义连接器,将起点、路由返回的点、终点加工为  元素的 d 属性,返回边在画布上渲染后的样式
        Graph.registerConnector(
          "algo-connector",
          (s, e) => {
            const offset = 4;
            const deltaY = Math.abs(e.y - s.y);
            const control = Math.floor((deltaY / 3) * 2);
    
            const v1 = { x: s.x, y: s.y + offset + control };
            const v2 = { x: e.x, y: e.y - offset - control };
    
            return Path.normalize(
              `M ${s.x} ${s.y} // 起始位置
               L ${s.x} ${s.y + offset} // 到达位置 
               C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${e.x} ${e.y - offset} // 曲线到
               L ${e.x} ${e.y} // 到达位置 
              `
            );
          },
          true
        );
        const graph = new Graph({
          container: container,
          width: "100%",
          height: "100%",
          background: {
            color: "#383838", opacity: "0.5"
    
          },
          grid: {
            size: 10, // 网格大小 10px
            visible: true, // 渲染网格背景
            type: "mesh",
            args: {
              color: "#5B5B5B",
            },
          },
          onPortRendered(args) { // 自定义连接桩
            const selectors = args.contentSelectors
            const container = selectors && selectors.foContent
            const portAttr = args.port.attrs.data
            if (container) {
              const root = ReactDOM.createRoot(container);
              root.render(
                <Tooltip title={portAttr.type}>
                  <CustomizePort portAttr={portAttr}></CustomizePort> {/* eslint-disable-next-line */}
                </Tooltip>,
              );
            }
          },
          history: true, // 撤销/重做,默认禁用
          snapline: {
            // 是否添加对齐线
            enabled: true,
            sharp: true,
          },
          // scroller: { // 画布是否可滚动
          //   enabled: true,
          //   pannable: true, // 是否启用画布平移能力
          //   autoResize: false, // 是否自动扩充/缩小画布
          // },
          mousewheel: {
            // 是否可用鼠标绽放
            enabled: true,
            modifiers: ["ctrl", "meta"],
          },
          highlighting: {
            // 触发某种交互时的高亮样式
            magnetAdsorbed: {
              // 连接桩可以被连接时高亮
              name: "stroke",
              args: {
                attrs: {
                  fill: "#fff",
                  stroke: "#31d0c6",
                  strokeWidth: 4,
                },
              },
            },
          },
          connecting: {
            // 配置全局的连线规则
            snap: true, // 是否自动吸附
            allowMulti: true, // 是否允许在相同的起始节点和终止之间创建多条边
            allowNode: false, // 是否允许边链接到节点(非节点上的链接桩)
            allowBlank: false, // 是否允许连接到空白点
            allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,
            allowEdge: false, // 是否允许边链接到另一个边
            highlight: true, // 拖动边时,是否高亮显示所有可用的连接桩或节点
            connector: "algo-connector", // 边渲染到画布后的样式
            connectionPoint: "anchor", // 指定连接点
            anchor: "center", // 指定被连接的节点的锚点
            // validateMagnet({ e, magnet, view, cell }) {
            //   // magnet 被按下时,是否创建新的边
            //   console.log("magent", e, magnet, view, cell);
            //   return false;
            // },
            createEdge() {
              // 连接的过程中创建新的边
              return graph.createEdge({
                shape: "dag-edge",
                attrs: {
                  line: {
                    strokeDasharray: "5 5",
                  },
                },
                zIndex: -1,
              });
            },
          },
          selecting: {
            enabled: true,
            className: 'my-selecting', // 附加样式名,用于定制样式,
            multiple: true, // 是否启用点击多选,默认为 true。启用多选后按住 ctrl 或 command 键点击节点实现多选。
            rubberband: false,  // 是否启用框选
            rubberNode: true,  // 自定义框选节点
            rubberEdge: true,  // 自定义框选边
            movable: true,  // 在多选情况下,选中的节点是否一起移动
            following: true, // 在多选情况下,选中的节点是否跟随鼠标实时移动
            showNodeSelectionBox: true, // 是否显示节点的选择框
            showEdgeSelectionBox: true, // 是否显示边的选择框
            strict: true, // 启用框选时,选框完全包围节点时才选中节点,否则只需要选框与节点的包围盒(BBox)相交即可选中节点
            content: (selection) => {
              return StringExt.template(
                '<%= length %> node<%= length > 1 ? "s":"" %> selected.',
              )({ length: selection.length })
            }
          },
        });
    
        graph.enableHistory();
        globalGraph = graph;
    
        graph.on('blank:click', ({ e, x, y }) => {
          console.log("fsfds")
          setRightModelShow(false)
    
    
        })
        // eslint-disable-next-line
      }, [container, CustomizePort]);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
  • 相关阅读:
    6. 从ods(贴源层)到 dwd(数据明细层)的两种处理方式(spark)-dsl
    思科防火墙高级应用
    450-500未传计算机毕业设计安卓App毕设项目之ssm公园植物介绍APP
    从零开始学习软件测试-第46天笔记
    理解Java泛型的复杂写法<? super T>,<? extend T>
    Flink实时数仓同步:实时表、流水表、快照表整合实战详解
    【区块链 | 预言机】从零开始使用Chainlink预言机(2)- 智能合约中使用更安全的随机数-代码实战
    [js电子榨菜]事件传递机制 event propogate
    OpenResty无损升级内嵌nginx版本0DAY漏洞
    直击产业落地 | 飞桨重磅推出业界首个模型选型工具
  • 原文地址:https://blog.csdn.net/weixin_38318244/article/details/126600237