Javascript D3:平移和缩放时保持SVG的相对位置

Javascript D3:平移和缩放时保持SVG的相对位置,javascript,d3.js,svg,Javascript,D3.js,Svg,我正在尝试对SVG平面图进行概念验证,该平面图可以平移和缩放,还可以在顶部放置标记。当缩放/平移发生时,标记不会保持在原位。我理解为什么会发生这种情况,但不确定在平移/缩放时保持标记位置的最佳方法 代码如下: var svg = d3.select(".floorplan") .attr("width", "100%") .attr("height", "100%") .call(d3.zoom().on("zoom", zoomed)) .select("g")

我正在尝试对SVG平面图进行概念验证,该平面图可以平移和缩放,还可以在顶部放置标记。当缩放/平移发生时,标记不会保持在原位。我理解为什么会发生这种情况,但不确定在平移/缩放时保持标记位置的最佳方法

代码如下:

    var svg = d3.select(".floorplan")
  .attr("width", "100%")
  .attr("height", "100%")
  .call(d3.zoom().on("zoom", zoomed))
  .select("g")

  var marker = d3.selectAll(".marker")
    .call(d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended)
    )


  function zoomed() {
    svg.attr("transform", d3.event.transform);
  }

  function dragstarted(d) {
    console.log('dragstarted');
  }

  function dragged(d) {
    var x = d3.event.x;
    var y = d3.event.y;

    d3.select(this).attr("transform", "translate(" + x + "," + y + ")");
  }

  function dragended(d) {
    console.log('drag ended: marker:'+ d3.select(this).attr('data-id') + ' position: ' + d3.event.x +', ' + d3.event.y);
  }
这里还有一个代码笔可以直观地看到:


我还有一个附加约束,即标记元素不应包含在floorplan svg中。

这里是代码笔的一个修改版本,它修复了拖动事件期间标记的移动,同时将标记保留在floorplan svg容器之外:

为了回到上下文中,一个简单的解决方案是在floorplan容器中包含marker元素,以便marker获得与floorplan相同的缩放事件,但这里我们希望marker位于其自己的svg容器中

这不是小事

为了从html中选择这些元素,只修改了javascript部分

让我们深入了解一下达到这一点所需的步骤:

首先:让我们修改缩放函数以应用于标记:

最初,这是缩放功能:

function zoomed() {
  svg.attr("transform", d3.event.transform);
}
function dragged(d) {
  var x = d3.event.x;
  var y = d3.event.y;
  d3.select(this).attr("transform", "translate(" + x + "," + y + ")");
}
以及修改后的版本:

function zoomed() {

  // Before zooming the floor, let's find the previous scale of the floor:
  var curFloor = document.getElementById('floorplan');
  var curFloorScale = 1;
  if (curFloor.getAttribute("transform")) {
    var curFloorTransf = getTransformation(curFloor.getAttribute("transform"));
    curFloorScale = curFloorTransf.scaleX;
  }

  // Let's apply the zoom
  svg.attr("transform", d3.event.transform);

  // And let's now find the new scale of the floor:
  var newFloorTransf = getTransformation(curFloor.getAttribute("transform"));
  var newFloorScale = newFloorTransf.scaleX;

  // This way we get the diff of scale applied to the floor, which we'll apply to the marker:
  var dscale = newFloorScale - curFloorScale;

  // Then let's find the current x, y coordinates of the marker:
  var marker = document.getElementById('Layer_1');
  var currentTransf = getTransformation(marker.getAttribute("transform"));
  var currentx = currentTransf.translateX;
  var currenty = currentTransf.translateY;

  // And the position of the mouse:
  var center = d3.mouse(marker);

  // In order to find out the distance between the mouse and the marker:
  // (43 is based on the size of the marker)
  var dx = currentx - center[0] + 43;
  var dy = currenty - center[1];

  // Which allows us to find out the exact place of the new x, y coordinates of the marker after the zoom:
  // 38.5 and 39.8 comes from the ratio between the size of the floor container and the marker container.
  // "/2" comes (I think) from the fact that the floor container is initially translated at the center of the screen:
  var newx = currentx + dx * dscale / (38.5/2);
  var newy = currenty + dy * dscale / (39.8/2);

  // And we can finally apply the translation/scale of the marker!:
  d3.selectAll(".marker").attr("transform", "translate(" + newx + "," + newy + ") scale(" + d3.event.transform.k + ")");
}
function draggedMarker(d) {

  var x = d3.event.x;
  var y = d3.event.y;

  // As we want to keep the same current scale of the marker during the transform, let's find out the current scale of the marker:
  var marker = document.getElementById('Layer_1');
  var curScale = 1;
  if (marker.getAttribute("transform")) {
    curScale = getTransformation(marker.getAttribute("transform")).scaleX;
  }

  // We can thus apply the translate And keep the current scale:
  d3.select(this).attr("transform", "translate(" + x + "," + y + "), scale(" + curScale + ")");
}
这大量使用了函数,该函数允许检索元素的当前转换详细信息

然后:但现在,缩放后,当我们拖动标记时,它会恢复其原始大小:

这意味着在应用拖动变换时,我们必须调整标记的dragg函数以保持其当前比例:

这是最初的拖动功能:

function zoomed() {
  svg.attr("transform", d3.event.transform);
}
function dragged(d) {
  var x = d3.event.x;
  var y = d3.event.y;
  d3.select(this).attr("transform", "translate(" + x + "," + y + ")");
}
及其修改版本:

function zoomed() {

  // Before zooming the floor, let's find the previous scale of the floor:
  var curFloor = document.getElementById('floorplan');
  var curFloorScale = 1;
  if (curFloor.getAttribute("transform")) {
    var curFloorTransf = getTransformation(curFloor.getAttribute("transform"));
    curFloorScale = curFloorTransf.scaleX;
  }

  // Let's apply the zoom
  svg.attr("transform", d3.event.transform);

  // And let's now find the new scale of the floor:
  var newFloorTransf = getTransformation(curFloor.getAttribute("transform"));
  var newFloorScale = newFloorTransf.scaleX;

  // This way we get the diff of scale applied to the floor, which we'll apply to the marker:
  var dscale = newFloorScale - curFloorScale;

  // Then let's find the current x, y coordinates of the marker:
  var marker = document.getElementById('Layer_1');
  var currentTransf = getTransformation(marker.getAttribute("transform"));
  var currentx = currentTransf.translateX;
  var currenty = currentTransf.translateY;

  // And the position of the mouse:
  var center = d3.mouse(marker);

  // In order to find out the distance between the mouse and the marker:
  // (43 is based on the size of the marker)
  var dx = currentx - center[0] + 43;
  var dy = currenty - center[1];

  // Which allows us to find out the exact place of the new x, y coordinates of the marker after the zoom:
  // 38.5 and 39.8 comes from the ratio between the size of the floor container and the marker container.
  // "/2" comes (I think) from the fact that the floor container is initially translated at the center of the screen:
  var newx = currentx + dx * dscale / (38.5/2);
  var newy = currenty + dy * dscale / (39.8/2);

  // And we can finally apply the translation/scale of the marker!:
  d3.selectAll(".marker").attr("transform", "translate(" + newx + "," + newy + ") scale(" + d3.event.transform.k + ")");
}
function draggedMarker(d) {

  var x = d3.event.x;
  var y = d3.event.y;

  // As we want to keep the same current scale of the marker during the transform, let's find out the current scale of the marker:
  var marker = document.getElementById('Layer_1');
  var curScale = 1;
  if (marker.getAttribute("transform")) {
    curScale = getTransformation(marker.getAttribute("transform")).scaleX;
  }

  // We can thus apply the translate And keep the current scale:
  d3.select(this).attr("transform", "translate(" + x + "," + y + "), scale(" + curScale + ")");
}
最后:拖动地板时,我们还必须相应地拖动标记:

因此,我们必须覆盖地板的默认拖动,以便将相同的拖动事件包含到标记中

以下是应用于地板的拖曳功能:

function draggedFloor(d) {

  // Overriding the floor drag to do the exact same thing as the default drag behaviour^^:

  var dx = d3.event.dx;
  var dy = d3.event.dy;

  var curFloor = document.getElementById('svg-floor');
  var curScale = 1;
  var curx = 0;
  var cury = 0;
  if (curFloor.getAttribute("transform")) {
    curScale = getTransformation(curFloor.getAttribute("transform")).scaleX;
    curx = getTransformation(curFloor.getAttribute("transform")).translateX;
    cury = getTransformation(curFloor.getAttribute("transform")).translateY;
  }

  d3.select(this).attr("transform", "translate(" + (curx + dx) + "," + (cury + dy) + ")");

  // We had to override the floor drag in order to include in the same method the drag of the marker:

  var marker = document.getElementById('Layer_1');
  var currentTransf = getTransformation(marker.getAttribute("transform"));

  var currentx = currentTransf.translateX;
  var currenty = currentTransf.translateY;
  var currentScale = currentTransf.scaleX;

  d3.selectAll(".marker").attr("transform", "translate(" + (currentx + dx) + "," + (currenty + dy) + ") scale(" + currentScale + ")");
}

嘿谢谢你的努力!非常感谢!我认为你说得对,这不是小事,我认为当有多个标记时会有性能问题!考虑到这一点,我认为我们将尝试找到一种解决方案,在floorplan SVG中添加标记,或者在视口中包装这两个SVG,并在该级别上进行缩放。再次感谢你的帮助!