在 JavaScript 中缩放/平移后拖动 SVG 元素时找到节点的正确位置

问题描述 投票:0回答:1

我正在制作一个脚本,使用 SVG 根据以下形式的输入绘制有向图

let data = {
  'A': {
    'children': ['B', 'C'],
    'parents': [],
    'coords': {
      'x': 10,
      'y': 10
    }
  },
  'B': {
    'children': ['C'],
    'parents': ['A'],
    'coords': {
      'x': 30,
      'y': 10
    }
  },
  'C': {
    'children': [],
    'parents': ['A', 'B'],
    'coords': {
      'x': 20,
      'y': 20
    }
  }
}

它使用三次贝塞尔曲线在父节点和子节点之间创建路径。这个想法是能够根据每个节点的“坐标”属性构建图形的可视化,然后允许用户通过拖放来实时移动节点。

我实现得很好,直到我添加了平移和缩放功能。现在,如果图像被平移和/或缩放,当我将元素的位置更新到光标位置时,它们会被放置在错误的位置。这是我目前必须更新位置的拖动功能

function startDrag(evt) {
    if (evt.target.classList.contains('draggable')) {
      selectedElement = evt.target;

      // we need to store the IDs of paths connecting to the nodes so that we can update their positions accordingly
      // Their IDs are stored as `${parent_key}_to_${child_key}`, e.g., #A_to_I
      path_ids = [];
      let node_key = selectedElement.getAttributeNS(null, 'id');
      for (let child_key of data[node_key]['children']) {
        path_ids.push(`${node_key}_to_${child_key}`);
      }
      for (let parent_key of data[node_key]['parents']) {
        path_ids.push(`${parent_key}_to_${node_key}`);
      }
    }
  }
  
  function drag(evt) {
    if (selectedElement) {
      evt.preventDefault();
      
      // we need zoom/pan information to reposition dragged nodes correctly
      ///////////////////////////////////////////////////
      // Potentially use some of this data to calculate correct positions ???
      let matrix = document.getElementById('scene').getAttributeNS(null, 'transform');
      let m = matrix.slice(7, matrix.length-1).split(' ');
      let zoomFactor = m[0];
      let panX = m[4];
      let panY = m[5];

      let svgBBox = svg.getBBox();
      ///////////////////////////////////////////////////

      // move the node itself
      selectedElement.setAttributeNS(null, 'cx', evt.clientX);
      selectedElement.setAttributeNS(null, 'cy', evt.clientY);
      
      // now for each path connected to the node, we need to update either the first vertex of the cubic bezier curve, or the final vertex
      // if id is ${clicked_node}_to_${other} then we change the first point, if it is ${other}_to_${clicked_node} then the last node
      let clicked_node = selectedElement.getAttributeNS(null, 'id');
      for (let path_id of path_ids) {
        let path = document.getElementById(path_id);
        let bez_d = path.getAttributeNS(null, 'd');
        let bez_split = bez_d.split(' ');
        if (path_id[0] === clicked_node) {
          let new_d = `M ${evt.clientX} ${evt.clientY} C ${evt.clientX},${evt.clientY}`;
          new_d += ` ${bez_split[5]} ${bez_split[6]}`;
          path.setAttributeNS(null, 'd', new_d);
        } else if (path_id[path_id.length - 1] === clicked_node) {
          let new_d = `M ${bez_split[1]} ${bez_split[2]} C ${bez_split[4]} ${bez_split[5]}`;
          new_d += ` ${evt.clientX},${evt.clientY}`;
          path.setAttributeNS(null, 'd', new_d);
        }
      }
    }
  }
  
  function endDrag(evt) {
    selectedElement = null;
    path_ids = [];
  }

正如您在 Drag() 函数中看到的,我能够在平移/缩放后从 svg 本身获取 bbox 数据,并且我能够获取容纳所有可拖动节点的 元素的变换矩阵。我认为可以用这些信息计算出正确的位置,但我不知道如何计算。

请参阅 https://jsfiddle.net/quamjxg7/ 获取完整代码。

简单地说:更新可拖动 SVG 元素的位置时如何考虑平移和缩放?

javascript svg graphics drag
1个回答
0
投票

哇,这个解决方案可以说是微不足道的。

我意识到我可以通过 元素的变换矩阵中的平移参数偏移光标位置来处理平移。然后我意识到我可以将其除以 ZoomingFactor 来考虑缩放。请参阅下面更新的拖动功能

function drag(evt) {
    if (selectedElement) {
      evt.preventDefault();
      
      // we need zoom/pan information to reposition dragged nodes correctly
      let matrix = document.getElementById('scene').getAttributeNS(null, 'transform');
      let m = matrix.slice(7, matrix.length-1).split(' ');
      let zoomFactor = m[0];
      let panX = m[4];
      let panY = m[5];

      let newX = (evt.clientX - panX)/zoomFactor;
      let newY = (evt.clientY - panY)/zoomFactor;

      // move the node itself
      selectedElement.setAttributeNS(null, 'cx', newX);
      selectedElement.setAttributeNS(null, 'cy', newY);
      
      // now for each path connected to the node, we need to update either the first vertex of the cubic bezier curve, or the final vertex
      // if id is ${clicked_node}_to_${other} then we change the first point, if it is ${other}_to_${clicked_node} then the last node
      let clicked_node = selectedElement.getAttributeNS(null, 'id');
      for (let path_id of path_ids) {
        let path = document.getElementById(path_id);
        let bez_d = path.getAttributeNS(null, 'd');
        let bez_split = bez_d.split(' ');
        if (path_id[0] === clicked_node) {
          let new_d = `M ${newX} ${newY} C ${newX},${newY}`;
          new_d += ` ${bez_split[5]} ${bez_split[6]}`;
          path.setAttributeNS(null, 'd', new_d);
        } else if (path_id[path_id.length - 1] === clicked_node) {
          let new_d = `M ${bez_split[1]} ${bez_split[2]} C ${bez_split[4]} ${bez_split[5]}`;
          new_d += ` ${newX},${newY}`;
          path.setAttributeNS(null, 'd', new_d);
        }
      }
    }
  }

已实施修复的完整代码示例 https://jsfiddle.net/x2e1wrLg/

© www.soinside.com 2019 - 2024. All rights reserved.