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.自定义连接桩

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 === "<class 'sklearn.base.ClassifierMixin'>") {
        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 === "<class 'pandas.core.frame.DataFrame'>") {
        // 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连接桩样式

然后来看我们的自定义连接桩,这里使用的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.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);

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

 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>,
          );
        }
      }
   })

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

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);
   }
 );

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

 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,
          });
        },
      },
  })

还有连线样式

 // 连接过程中产生的新边的样式
    Graph.registerEdge(
      "dag-edge",
      {
        inherit: "edge",
        attrs: {
          line: {
            stroke: "#9DADB6",
            strokeWidth: 2,
            sourceMarker: null,
            targetMarker: {
              //
              name: "block", // 实心箭头
            },
          },
        },
      },
      true
    );

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

3.1被选中时回传选中数据

graph.on("cell:selected", ({ cell, options }) => {
  const allSelected = graph.getSelectedCells();
  console.log('allSelected============', allSelected)
  setSelectJSON(allSelected);
});

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

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

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

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

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 })
        }
      },
    });

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

 //官方文档写的是componentDidMount,因为react取消了三个生命周期函数,所以使用useEffect
  useEffect(() => {
    // 连接过程中产生的新边的样式
    Graph.registerEdge(
      "dag-edge",
      {
        inherit: "edge",
        attrs: {
          line: {
            stroke: "#9DADB6",
            strokeWidth: 2,
            sourceMarker: null,
            targetMarker: {
              //
              name: "block", // 实心箭头
            },
          },
        },
      },
      true
    );

    // 自定义连接器,将起点、路由返回的点、终点加工为 <path> 元素的 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
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值