Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wangtao-bugkiller opened this issue Nov 12, 2024 · 7 comments

Comments

@wangtao-bugkiller
Copy link

Describe the bug / 问题描述

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

Reproduction link / 重现链接

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

@github-actions github-actions bot changed the title 脑图里面拖拽节点的时候,去插入一个临时节点来显示当前插入的位置,总是卡死无法继续 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 Nov 12, 2024
@wangtao-bugkiller
Copy link
Author

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

@wangtao-bugkiller
Copy link
Author

image

@wangtao-bugkiller
Copy link
Author

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

@wangtao-bugkiller
Copy link
Author

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

@wangtao-bugkiller
Copy link
Author

麻烦帮忙看一下

@Aarebecca
Copy link
Contributor

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

@wangtao-bugkiller
Copy link
Author

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants