AntV x6

环境:Ant design pro v4 js

antV x6:1.21.7

实现以下功能:节点划线、自动布局、右键菜单

代码可能存在bug,抛砖引玉吧

参考官网demo:业务实践 | X6

右键菜单demo:https://x6.antv.vision/zh/examples/edge/tool#context-menu

import React, { useEffect, useState, useRef } from 'react'
import { Graph, Node, ToolsView } from '@antv/x6'
import dagre from 'dagre'
import { Menu, Modal, message, Dropdown } from 'antd';
import './shape';
import styles from './canvas-content.less'
import { FileAddOutlined, DeleteOutlined, DownSquareOutlined, UpSquareOutlined } from '@ant-design/icons';

/**
 * 参考案例
 * https://x6.antv.vision/zh/examples/showcase/practices#orgchart
 */
const CanvasContent = (props) => {
  const {
    data,
    setKnowledgeValues
  } = props;

  let graph = {}
  useEffect(() => {
    console.log('useEffect 2', graph)
    graph = new Graph({
      container: document.getElementById('mind-content'),
      grid: true,
      interacting: {
        nodeMovable: false, //节点禁止拖动
        edgeMovable: false //边禁止拖动
      },
      selecting: {
        enabled: true,//开启点击
        multiple: false,//禁止多选
        rubberband: false,//禁用框选
        movable: false,//禁止连带移动
        showNodeSelectionBox: true,//开启选择后效果
      },      
    })
    graph.zoom(-0.1)//缩放比例
    data.map((info, index) => {
      // debugger
      const parentNode = createNode(info)
      forEachChild(info, parentNode)
    })
    layout();
    setup();
  }, [data]);

  /**
   * 递归创建节点
   * @param {*} info api data
   * @param {*} parentNode X6 node
   */
  const forEachChild = (info, parentNode) => {
    // debugger
    info.children.map((childInfo, childIndex) => {
      const childNode = createNode(childInfo)
      createEdge(parentNode, childNode)
      forEachChild(childInfo, childNode)
    })
  }
  // 添加节点
  const createNode = (info) => {
    return graph.addNode({
      shape: 'mind-map-rect',
      attrs: {
        text: {
          textWrap: {
            text: info.text,
            id: info.id
          },
        },
      }
    })
  }
  // 添加连线
  const createEdge = (source, target, vertices) => {
    return graph.addEdge({
      shape: 'org-edge',
      source: { cell: source },
      target: { cell: target },
    })
  }
  // 自动布局
  const layout = () => {
    const nodes = graph.getNodes()
    const edges = graph.getEdges()
    const g = new dagre.graphlib.Graph()
    g.setGraph({ rankdir: 'LR', nodesep: 16, ranksep: 16 })
    g.setDefaultEdgeLabel(() => ({}))

    const width = 260
    const height = 40
    // debugger;
    nodes.forEach((node) => {
      g.setNode(node.id, { width, height })
    })

    edges.forEach((edge) => {
      const source = edge.getSource()
      const target = edge.getTarget()
      g.setEdge(source.cell, target.cell)
    })

    dagre.layout(g)

    graph.freeze()

    g.nodes().forEach((id) => {
      const node = graph.getCell(id)
      if (node) {
        const pos = g.node(id)
        node.position(pos.x, pos.y)
      }
    })

    edges.forEach((edge) => {
      const source = edge.getSourceNode()
      const target = edge.getTargetNode()
      const sourceBBox = source.getBBox()
      const targetBBox = target.getBBox()

      console.log(sourceBBox, targetBBox)

      const gap = targetBBox.x - sourceBBox.x - sourceBBox.width
      const fix = sourceBBox.width
      const x = sourceBBox.x + fix + gap / 2
      edge.setVertices([
        { x, y: sourceBBox.center.y },
        { x, y: targetBBox.center.y },
      ])
    })
    graph.unfreeze()
  }

  // 监听自定义事件
  const setup = () => {
    //双击事件
    graph.on('node:dblclick', ({ cell, view }) => {
      const oldText = cell.attr('text/textWrap/text')
      const elem = view.container.querySelector('.x6-edit-text')
      if (elem == null) { return }
      cell.attr('text/style/display', 'none')
      if (elem) {
        elem.style.display = ''
        elem.contentEditable = 'true'
        elem.innerText = oldText
        elem.focus()
      }
      const onBlur = () => {
        cell.attr('text/textWrap/text', elem.innerText)
        cell.attr('text/style/display', '')
        elem.style.display = 'none'
        elem.contentEditable = 'false'
        console.log('onBlur = ', elem.innerText)
      }
      elem.addEventListener('blur', () => {
        onBlur()
        elem.removeEventListener('blur', onBlur)
      })
    })
    //右键菜单
    graph.on('node:contextmenu', ({ cell, e }) => {
      console.log('cell ', cell.attr('text/textWrap/text'), cell)
      //default menu status
      var delFlag = false, topFlag = false, downFlag = false;
      if (graph.isRootNode(cell)) {
        //is root node
        delFlag = true, topFlag = true, downFlag = true
      } else {
        //查找同层的集合
        const id = cell.attr('text/textWrap/id')
        const sameLevelList = getSameLevel(data[0].children, id);

        if (sameLevelList.length === 1) {
          //同层只有1个元素,禁用上下
          topFlag = true, downFlag = true
        } else {
          const index = sameLevelList.findIndex(info => info.id === id);
          if (index === 0) topFlag = true //最上层禁用上移
          else if (index === sameLevelList.length - 1) downFlag = true //最下层禁用下移
        }
      }
      const p = graph.clientToGraph(e.clientX, e.clientY)
      const menu = (
        <Menu className={styles.x6Menu} onClick={(e) => handleMenuClick(e, cell)}>
          <Menu.Item key="1" icon={<FileAddOutlined />}>添加</Menu.Item>
          <Menu.Item key="2" icon={<DeleteOutlined />} disabled={delFlag}>删除</Menu.Item>
          <Menu.Item key="3" icon={<UpSquareOutlined />} disabled={topFlag}>上移</Menu.Item>
          <Menu.Item key="4" icon={<DownSquareOutlined />} disabled={downFlag}>下移</Menu.Item>
        </Menu>
      )
      // debugger
      cell.addTools([
        {
          name: 'contextmenu',
          args: {
            menu,
            x: p.x,
            y: p.y,
            onHide() {
              // this.cell.removeTools()
              cell.removeTools()
            },
          },
        },
      ])
    })
  }
  /**
 * 递归根据id查询当前对象
 * @param {*} list 
 * @param {*} id 
 * @returns 
 */
  const getKnowLedgeInfo = (list, id) => {
    let result = false;
    list.map((item, index) => {
      if (result) return;
      if (item.id === id) {
        list.splice(index, 1)
        result = true;
        return;
      }
      return getKnowLedgeInfo(item.children, id)
    })
  }
  /**
   * 菜单点击事件
   * @param {*} event 点击事件
   * @param {*} node 选中当前节点
   */
  const handleMenuClick = (event, node) => {
    const { key } = event;
    const id = node.attr('text/textWrap/id');
    if (key === '1') {
      //添加节点
      const newNode = createNode('New Node')
      graph.freeze()
      graph.addCell([newNode, createEdge(node, newNode)])
      layout()
    } else if (key === '2') {
      //删除节点
      Modal.confirm({
        title: '删除知识点',
        content: `确定删除 “${node.attr('text/textWrap/text')}” 和 “子节点” 吗?`,
        okText: '确认',
        cancelText: '取消',
        onOk: () => {
          graph.freeze()
          const cells = graph.getSuccessors(node);//获取后续节点
          graph.removeCells(cells) //删除子节点
          graph.removeCell(node) //删除当前节点
          layout()
        },
      });
    } else if (key === '3') {
      //上移节点
      //查找同级别元素
      const sameLevelList = getSameLevel(data[0].children, id);
      //获取选中元素下表
      const index = sameLevelList.findIndex(info => info.id === id);
      //删除top元素,把选中元素添加到top位置,并返回被删除的top元素
      const topElement = sameLevelList.splice(index - 1, 1, sameLevelList[index])[0]
      //将被删除的top元素插入到触发节点位置
      sameLevelList.splice(index, 1, topElement) //
      console.log('sameLevelList ', sameLevelList)
    } else if (key === '4') {
      //下移节点        
    }
  }
  const getSameLevel = (dataList, id) => {
    let sameLevelList = []
    //根节点的子类遍历,根节点不允许上下移动
    dataList.forEach((info) => {
      if (info.id === id) {
        sameLevelList = dataList;
        return;
      }
      if (sameLevelList.length > 0) return
      sameLevelList = getSameLevel(info.children, id)
    })
    return sameLevelList;
  }
  return (
    <div className={styles.mindDiv}>
      <div className={styles.mindContent} id="mind-content" />
    </div>
  )
}

export default CanvasContent;

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
ANTV X6组态是一款先进的工业视觉软件,可以实现对工业设备和生产过程的监控和控制。该软件具有以下几个主要特点和功能。 首先,ANTV X6组态具有强大的数据采集和分析能力。它可以实时采集各种传感器和设备的数据,并对其进行处理和分析,帮助用户了解设备运行状态和生产过程中的关键指标。用户可以通过直观的界面和仪表板,随时查看设备状态并进行数据分析,以便及时对设备进行维护和优化生产过程。 其次,ANTV X6组态具有灵活的报警和监控功能。用户可以设置各种报警条件和规则,当设备发生异常或超出预设范围时,系统会立即发出警报通知用户。同时,用户可以通过远程监控功能,随时远程查看设备状态,并进行远程控制和操作,实现远程管理和控制。 此外,ANTV X6组态支持多种通讯协议和接口,可以与各种设备和系统进行无缝集成。用户可以轻松连接不同厂商和类型的设备,实现设备之间的数据共享和相互控制,提高生产效率和设备运行稳定性。 最后,ANTV X6组态还提供了丰富的可视化和人机交互功能。用户可以通过拖拽和配置界面,自定义展示和操作界面,以适应不同应用场景和用户需求。同时,软件还支持触摸屏和移动设备等多种操作方式,使用户可以随时随地进行设备监控和控制。 总之,ANTV X6组态是一款功能强大、灵活可靠的工业视觉软件,为用户提供了全面的设备监控和控制解决方案,帮助提高生产效率和设备运行稳定性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值