当前位置: 首页 > news >正文

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

相关文章:

  • TIA博途V17中ProDiag功能的使用方法示例(一)PLC数据类型的监控
  • 面试常见场景题智力题概率题
  • 【顶顶通呼叫中心中间件(mod_cti 基于 FreeSWITCH)-拨号方案和路由配置】
  • M1Mac使用UTM虚拟机最小化安装x86_64架构的Archlinux
  • sql2java:WhereHelper基于Beanshell(bsh)动态生成SQL语句
  • 谷歌推广详细教程,Google Ads广告投放指南
  • 蔡甸17万亩粮田丰收 国稻种芯:夏汛蓄洪水护住28天抗旱期
  • 比赛团队队名及口号
  • MECT4CNER 代码遇到的问题
  • 18. SAP ABAP OData 服务嵌套创建功能的实现步骤(Create Deep)
  • 优炫软件中标西南民族大学项目,护航教育行业主机安全
  • 网课搜题公众号免费搭建
  • 【深度学习】——深度学习中基本的网络结构(1)
  • 神经网络如何避免过拟合,人工神经网络过拟合
  • 大学生搜题公众号如何搭建
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • co.js - 让异步代码同步化
  • Hibernate【inverse和cascade属性】知识要点
  • javascript数组去重/查找/插入/删除
  • Vue官网教程学习过程中值得记录的一些事情
  • Vultr 教程目录
  • 从重复到重用
  • 大快搜索数据爬虫技术实例安装教学篇
  • 基于axios的vue插件,让http请求更简单
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 如何用Ubuntu和Xen来设置Kubernetes?
  • 使用docker-compose进行多节点部署
  • 事件委托的小应用
  • 问题之ssh中Host key verification failed的解决
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • HanLP分词命名实体提取详解
  • Nginx实现动静分离
  • ​iOS安全加固方法及实现
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • # 手柄编程_北通阿修罗3动手评:一款兼具功能、操控性的电竞手柄
  • #大学#套接字
  • (07)Hive——窗口函数详解
  • (2)STL算法之元素计数
  • (2015)JS ES6 必知的十个 特性
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (4)STL算法之比较
  • (C语言)字符分类函数
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第2节(共同的基类)
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (二)基于wpr_simulation 的Ros机器人运动控制,gazebo仿真
  • (分布式缓存)Redis持久化
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (一) storm的集群安装与配置
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • (转)德国人的记事本
  • .Net MVC + EF搭建学生管理系统
  • .NET 中 GetHashCode 的哈希值有多大概率会相同(哈希碰撞)
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)...