antvis/G6

When dragging a node in the brain map, insert a temporary node to show the current inserted position, but it is always stuck and cannot be continued #6509

wangtao-bugkiller posted onGitHub

Describe the bug / 问题描述

脑图里面拖拽节点的时候,去插入一个临时节点来显示当前插入的位置,总是卡死无法继续

No response

Steps to Reproduce the Bug or Issue / 重现步骤

let dragNodeOriPos, minDisNode, minDisNodeId, dragRect, insertPos, insertIndex, lastInsertPostion, lastMinDisNode, lastMinDisNodeId, targetIndex, newParentId, newParentNode, targetInsertIndex, targetParentID;

/**

  • 检查拖动的节点与其他节点是否发生碰撞

  • @param {Object} node - 被检查的节点对象

  • @param {Object} rect - 拖动矩形的属性,包含x、y、width、height

  • @param {number} x - 拖动矩形的x坐标

  • @param {number} y - 拖动矩形的y坐标

  • @returns {boolean|Object} - 如果没有碰撞返回false,如果发生碰撞返回包含插入位置和碰撞状态的对象

  • / const collide = (node, rect, x, y) => { // 如果节点不存在,直接返回false if (!node) return false;

    // 获取节点的边界框,并添加padding以扩大碰撞检测范围 var nodeBBox = node.getBBox(), padding = 10, expandedNodeBBox = {

    x: nodeBBox.minX - padding,
    y: nodeBBox.minY - padding,
    width: nodeBBox.width + 2 * padding,
    height: nodeBBox.height + 2 * padding,

    }, // 构造拖动矩形的边界框,高度翻倍以适应特定的碰撞检测需求 draggedRect = {

    x: rect.x - padding,
    y: rect.y - padding,
    width: rect.width + 2 * padding,
    height: 2 * rect.height,

    };

    // 判断拖动过程中是否与其他结点发生碰撞 var isColliding = expandedNodeBBox.x < draggedRect.x + draggedRect.width && expandedNodeBBox.x + expandedNodeBBox.width > draggedRect.x && expandedNodeBBox.y < draggedRect.y + draggedRect.height && expandedNodeBBox.y + expandedNodeBBox.height > draggedRect.y;

    // 如果发生碰撞,根据拖动位置确定插入位置 if (isColliding) { var insertPosition =

    x > nodeBBox.maxX
      ? 'right'
      : x < nodeBBox.maxX && x > nodeBBox.minX
      ? y > nodeBBox.centerY
        ? 'bottom'
        : 'top'
      : 'left';

    // 返回插入位置和碰撞状态 return { insertPos: insertPosition, collide: true }; }

    // 如果没有碰撞,返回false return false; };

    /**

  • 遍历节点函数

  • 该函数用于在思维导图中找到并返回一个指定的节点及其索引位置

  • @param {Object} item - 需要查找的节点项,必须包含getID方法以获取节点ID

  • @param {Function} callback - 回调函数,未在本代码段中使用,但可能为未来扩展预留

  • @returns {Object} 返回一个对象,包含新父节点和目标索引

  • / const traversNodes = (item, callback) => { // 初始化新节点和新索引变量 let newNode, newIndex = 0;

    // 获取需要查找的节点的ID const id = item.getID();

    // 使用G6工具类的树遍历方法,从保存的思维导图树结构中从上至下查找节点 G6.Util.traverseTreeUp(mindMapTree.save(), function(node, parent, index) { // 当找到节点ID匹配的节点时,保存该节点及其索引,并停止继续遍历 if (node.id === id) {

    newNode = node;
    newIndex = index;
    return false;

    } else {

    // 如果当前节点不匹配,继续遍历下一个节点
    return true;

    } });

    // 返回结果对象,包含找到的节点和其索引位置 return { newParentNode: newNode, targetIndex: newIndex, }; };

    const traversRootNodes = e => { var found = false, nodeId = e.getID(); // 遍历树的根节点以判断是否为根节点 G6.Util.traverseTree(mindMapTree.save(), function(node, isRoot) { if (!isRoot || node.id !== nodeId) {

    found = true;
    return false;

    } }); return found; };

    const registerBehavior = () => { G6.registerBehavior('dice-mindmap', { getEvents() {

    return {
      'node:click': 'clickNode',
      'node:dblclick': 'editNode',
      'node:mouseenter': 'hoverNode',
      'node:mouseleave': 'hoverNodeOut',
    };

    }, clickNode(evt) {

    const model = evt.item.get('model');
    const name = evt.target.get('action') || '';
    const parent = evt.item.get('parent');
    const { x, y, images } = model;
    const image = Array.isArray(images) ? [...images] : [];
    const point = mindMapTree.getCanvasByPoint(x, y);
    setNodeContextMenuX(point.x);
    setNodeContextMenuY(point.y + 40);
    if (model.id !== currentNode?.get('model')?.id) {
      setShowDeleteMenu(false);
    }
    setCurrentNode(evt);
    const regex = /previewPic(\d+)/;
    const match = name.match(regex);
    const regexDel = /deletePic(\d+)/;
    const matchDel = name.match(regexDel);
    
    if (match) {
      const urlIndex = parseInt(match[1]);
      setPicPreviewOpen(true);
      setPreviewPicUrl(image[urlIndex].url);
    }
    if (matchDel) {
      const delIndex = parseInt(matchDel[1]);
      image.splice(delIndex, 1);
      deletePic(evt.item, image);
    }
    switch (name) {
      case 'expand':
      case 'collapse':
        mindMapTree.updateItem(
          evt.item,
          {
            collapsed: name === 'expand' ? false : true,
          },
          true
        );
        mindMapTree.setItemState(evt.item, 'collapsed', name === 'expand' ? false : true);
        mindMapTree.layout();
        break;
      case 'deleteTargetTag':
        const { id } = evt.target.cfg;
        setTargetTag(id);
        setShowDeleteMenu(true);
        break;
      default:
        return;
    }

    }, editNode(evt) {

    handleEditNode(evt, true, false);

    }, hoverNode(evt) {

    try {
      mindMapTree.setItemState(evt.item, 'hover', true);
    } catch (error) {
      console.log(error);
    }

    }, hoverNodeOut(evt) {

    try {
      mindMapTree.setItemState(evt.item, 'hover', false);
    } catch (error) {
      console.log(error);
    }

    }, });

    G6.registerBehavior('dice-mindmap-drag', { getEvents() {

    return {
      'node:dragstart': 'handleItemDragStart',
      'node:drag': 'handleItemDrag',
      'node:dragend': 'handleItemDragEnd',
    };

    },

    handleItemDragStart(evt) {

    if (!isEditMode()) return;
    const { item, x, y } = evt;
    const model = item.get('model');
    if (model.id === 'RootNode') {
      return;
    }
    minDisNode = null;
    dragNodeOriPos = { x: model.x, y: model.y };
    const { minX, minY, width, height } = item.getBBox();
    dragRect = {
      deltaX: x - minX,
      deltaY: y - minY,
      width,
      height,
    };
    if (!model.collapsed && model.children && model.children.length > 0) {
      model.collapsed = true;
      mindMapTree.setItemState(item, 'collapsed', true);
      mindMapTree.refreshItem(item);
    }
    
    // mindMapTree.hideItem(item, false);
    // mindMapTree.refreshPositions();

    },

    /**

    • 处理思维导图节点拖拽事件

    • @param {Object} evt - 拖拽事件对象,包含拖拽的相关信息

    • / handleItemDrag(evt) { // 获取当前拖拽项的模型信息 const model = evt.item.get('model'); if (model.id === 'RootNode') { return; } // 计算拖拽项的临时位置和中心点坐标 const tempBox = { width: dragRect.width, height: dragRect.height, x: evt.x - dragRect.deltaX, y: evt.y - dragRect.deltaY, centerX: evt.x - dragRect.deltaX + dragRect.width / 2, centerY: evt.y - dragRect.deltaY + dragRect.height / 2, };

      // 初始化最近距离节点变量 minDisNode = null;

      // 遍历思维导图树,检查拖拽节点与现有节点是否发生碰撞 G6.Util.traverseTreeUp(mindMapTree.save(), function(node) { // 跳过当前拖拽节点和临时节点 if (node.id === model.id || node.id === 'temp') {

      return true;

      }

      // 获取当前遍历的节点对象 const currentNode = mindMapTree.findById(node.id);

      // 检查当前节点与拖拽节点是否发生碰撞 const hascollided = collide(currentNode, tempBox, evt.x, evt.y); if (hascollided) {

      // 如果发生碰撞,更新最近距离节点及其状态
      minDisNode = currentNode;
      minDisNodeId = node.id;
      insertPos = hascollided.insertPos;
      console.log(hascollided, node.title);
      return false;

      } });

      // 如果找到最近距离节点,更新其子节点或调整节点位置 if (minDisNode) { const minx = minDisNode.getBBox().minX; mindMapTree.findDataById('temp') && mindMapTree.removeChild('temp', false); const tempNode = {

      id: 'temp',
      type: 'dice-mind-map-temp',
      title: '',

      };

      // 根据拖拽位置更新节点或调整节点位置 if (minx <= evt.x) {

      if (minDisNodeId === lastMinDisNodeId && lastInsertPostion === insertPos) {
        return;
      }
      
      mindMapTree.findDataById('temp') && mindMapTree.removeChild('temp', false);
      
      const targetNode = mindMapTree.findDataById(minDisNodeId) || { children: [] };
      const tagerChild = cloneDeep(targetNode.children || []);
      tagerChild.push(tempNode);
      targetParentID = minDisNode.getID();
      // if (minDisNode.getModel().collapsed) {
      //   minDisNode.getModel().collapsed = false;
      // }
      targetInsertIndex = tagerChild.length - 1;
      console.log(targetNode.children,tagerChild, 'c',targetInsertIndex);
      // mindMapTree.updateItem(minDisNodeId,{children: tagerChild},false);
      // mindMapTree.layout();
      mindMapTree.updateChildren(tagerChild, minDisNodeId);

      } else {

      if (minDisNodeId === lastMinDisNodeId && lastInsertPostion === insertPos) {
        return;
      }
      
      const traversedNodes = traversNodes(minDisNode);
      newParentNode = traversedNodes.newParentNode;
      targetIndex = traversedNodes.targetIndex;
      newParentId = newParentNode?.id;
      if (!newParentId) {
        return;
      }
      
      mindMapTree.findDataById('temp') && mindMapTree.removeChild('temp', false);
      
      const childrenArray = cloneDeep(newParentNode.children || []);
      insertIndex = insertPos === 'top' ? targetIndex : targetIndex + 1;
      childrenArray.splice(insertIndex, 0, tempNode);
      console.log(childrenArray, 'p',insertIndex);
      // mindMapTree.updateItem(newParentId,{children: childrenArray},false);
      // mindMapTree.layout();
      mindMapTree.updateChildren(childrenArray, newParentId);
      targetParentID = newParentId;
      targetInsertIndex = insertIndex;

      }

      // 更新拖拽项的位置 evt.item.updatePosition({

      x: dragRect.deltaX,
      y: dragRect.deltaY,

      });

      // 更新最近距离节点及其插入位置的缓存 lastMinDisNodeId = minDisNodeId; lastInsertPostion = insertPos; } }, handleItemDragEnd(evt) {}, }); };

G6 Version / G6 版本

4.x

Operating System / 操作系统

macOS

Browser / 浏览器

Chrome

Additional context / 补充说明

No response


是想在拖动的过程中实时的更新这个节点将要插入的位置,每次一插入临时节点就卡死了

posted by wangtao-bugkiller 5 months ago

image

posted by wangtao-bugkiller 5 months ago

const treeLayout = { type: 'mindmap', direction: 'LR', getHeight: node => { const { images, title, id } = node; let textArr = splitTitle(title); let textHeight = textArr.length * 26 + 20; if (images && images?.length > 0) { textArr += 52; }

  return textHeight;
},
getWidth: node => {
  const { depth, images, marks, nodeType, title } = node;
  const textWidth = depth === 0 ? getTextSize(title, 30)[0] : getTextSize(title, 22)[0];
  // 对 marks 进行边界条件检查
  let tagsWidth = getTagListLength(marks);
  const nodeTypeWidth = nodeType === 'CATA' ? 30 : 0;
  const imageLength = images ? images.length * 52 : 0;
  // 确保 textWidth 的值在合理的范围内,以避免潜在的计算错误
  const adjustedTextWidth = textWidth > 800 ? 900 : textWidth;
  const width = Math.max(adjustedTextWidth + tagsWidth + nodeTypeWidth, imageLength);
  return width;
},
getVGap: d => {
  const { children, depth, images } = d;
  if (Array.isArray(images) && images.length > 0) {
    return 40;
  }
  return 20;
},
getHGap: d => {
  const { children, depth } = d;
  if (!depth) {
    return 60;
  }
  const times = {
    0: 10,
    1: 9,
    2: 7,
    3: 6,
  };
  if (children && children.length > 0) {
    return children.length * (times[depth] || 6) + (children.length < 5 ? 40 : 10);
  }

  return 30;
},
getSide: node => {
  return 'right';
},

}; const renderG6 = () => { const grid = new G6.Grid(); const minimap = new G6.Minimap({ size: [150, 150], className: 'g6-minimap', type: 'delegate', hideEdge: true, });

const container = document.getElementById('xmindContainer');
const width = container.getBoundingClientRect().width;
const height = container.getBoundingClientRect().height;
setDivWH({ width, height });
mindMapTree = new G6.TreeGraph({
  container: 'xmindContainer',
  width,
  height,
  // fitView: true,
  fitCenter: true,
  enabledStack: true,
  maxStep: 20,
  // autoRefresh: true,
  // fitViewPadding: [10, 20],
  layout: treeLayout,
  defaultEdge: {
    type: 'cubic-horizontal',
    style: {
      lineWidth: 2,
      stroke: '#CED4D9',
    },
  },
  animate: false,
  animateCfg: {
    duration: 300, // Number,一次动画的时长
  },
  defaultNode: {
    type: 'dice-mind-map-root',
  },
  minZoom: 0.5,
  modes: {
    default: [
      {
        type: 'drag-node',
        enableDelegate: true,
        enableStack: false,
        enableDebounce: true,
        // shouldUpdate: function(e, self) {
        //   return true;
        // },
      },
      {
        type: 'collapse-expand',
        onChange: function onChange(item, collapsed) {
          const data = item.get('model');
          data.collapsed = collapsed;
          return true;
        },
        shouldBegin: (e, self) => {
          return false;
        },
      },
      'wheel-scroll',
      'scroll-canvas',
      'dice-mindmap',
      'dice-mindmap-drag',
      {
        type: 'click-select',
        trigger: 'shift',
        shouldBegin: (e, self) => {
          const actionsList = ['expand', 'collapse', 'deleteTargetTag'];
          const nameList = ['collapse-icon'];
          const status = e.item?._cfg?.states || [];
          const action = e.target?.cfg?.action || '';
          const regex = /previewPic(\d+)/;
          const match = action.match(regex);
          const regexDel = /deletePic(\d+)/;
          const matchDel = action.match(regexDel);
          const name = e.target?.cfg?.name || '';

          if (
            (nameList.includes(name) || match || matchDel || actionsList.includes(action)) &&
            status.includes('selected')
          )
            return false;
          return true;
        },
      },
      {
        type: 'brush-select',
        trigger: 'drag',
        includeEdges: false,
      },
    ],
  },
  plugins: [minimap, grid],
});

mindMapTree.data(dataTransform(xmindData || {}));

mindMapTree.render(); };

posted by wangtao-bugkiller 5 months ago

麻烦帮忙看下,拖拽改变节点的顺序,得有一个临时节点在拖拽过程中实时插入图里显示当前插入位置,就像xmind 软件里面拖拽节点的功能那样,我在插入临时节点的时候渲染就卡住了,不知道怎么解决

posted by wangtao-bugkiller 5 months ago

麻烦帮忙看一下

posted by wangtao-bugkiller 5 months ago

@wangtao-bugkiller 麻烦提供一个可以访问的线上 demo

posted by Aarebecca 5 months ago

@wangtao-bugkiller 麻烦提供一个可以访问的线上 demo

https://codesandbox.io/p/sandbox/sleepy-hooks-rlks3w?workspaceId=3c65d4d0-3ed1-4d5e-882b-b9fa0b0c9753 渲染逻辑大概是这个样子,现在这个demo里面没发拖

posted by wangtao-bugkiller 5 months ago

ce

posted by wangtao-bugkiller 5 months ago

Fund this Issue

$0.00
Funded

Pull requests