如何正确使用d3.js拖动?

How to use d3.js drag properly?

我正在使用 d3.js 和 Vue js 创建平面图。我想添加一个功能,用户单击一个按钮创建一个矩形(门口),用户可以更改比例和 X、Y 位置。我对 vue js 和 d3.js 很陌生,所以我使用 this 作为参考。该按钮起作用并为 X 次按下添加 X 次矩形。但是,当我添加第一个门口并尝试拖动矩形时,它完全偏离了用户的鼠标坐标。此外,当我添加第二个、第三个、第四个等矩形时,我无法更改它们的位置,只能更改第一个矩形。我认为这是因为我在拖动功能中只选择了一个矩形。但是,我更关心如何在矩形上正确拖动。有人能给我指出正确的方向吗?

<template>
  <div id = "app">

    <header>
      <Navbar />
    </header>

      <div class = "floor"> 
        <div id = "tooltip"></div>
        <svg width = "850" height = "800">
        </svg>
      </div>
    
    <div class = "buttons"> 
      <button v-on:click="addRec()"> Add Doorway </button>
    </div>

    
  </div>
</template>
addRec(){

        console.log("Doorway Added")
        var doorGroup = d3.selectAll("svg")
          .append("g")

        //setting attributes for doorway
        var doorway = doorGroup.append("rect")
          .attr("width",100)
          .attr("height", 25)
          .attr("x", 150)
          .attr("y", 200)

        //setting style attributes for doorway
          .attr("fill","#F5F5F5")
            
          .attr("stroke", "black")
            .attr("stroke-width", 2.5)
          .attr("cursor", "move")
          
          //binding drag method onto rectangle
          .call(d3.drag()
            .on('start', dragStart)
            .on('drag', dragging)
            .on('end', dragEnd)
          )
          
      },
      //this is to be called once used has clicked on the doorway
      dragStart(d,i,nodes){
        d3.select(nodes[i])
          .style("stroke", "red")
      },
      /*
      this is to be called once used has started dragging the doorway
      dragging(d) upadtes the position of the doorway, from the mouse X,Y Coors
      */
      dragging(d,i,nodes){
        var width = 850
        var height = 800
        var xCoor = d3.pointer(event)[0]
        var yCoor = d3.pointer(event)[1]

        // d3.select(nodes[i])
        //   .attr("transform", "translate(" + (xCoor - 200) + "," + (yCoor - 300) + ")")

        d3.select(nodes[i])
          .attr("x", xCoor)
          .attr("y", yCoor)
      },
      //this is to be called once user has clicked off the doorway
      dragEnd(d,i,nodes){
        d3.select(nodes[i])
          .style("stroke", "black")
      },

您的代码中有两个问题:

  1. 你只能select同一个矩形:

你所有的拖动函数都使用:

d3.select("rect")

这将 select 第一个匹配元素 DOM。这将始终是相同的矩形,它与被拖动的矩形不对应。相反,您可以通过以下方式访问正确的矩形:

function dragFunction() {
  d3.select(this);
}

在 d3v5 及更早版本中,如果 this 不可用,您可以使用:

dragFunction(d,i,nodes) => d3.select(nodes[i])

如果您在 d3v6 中无法访问 this,则没有获取矩形的好方法 - migration guide 显示了一种“旧”获取方式this 涉及为基准匹配过滤元素。我从未见过 d3v5 或更早版本中使用的方法,如果 this 不可用,d3.select(nodes[i]) 总是回退。因此,理想情况下,您可以访问 this - 不要对侦听器使用箭头函数

  1. 您通过 x,y 应用变换和定位。

最初附加您按 x,y 定位的矩形时。拖动时应用平移,但不要删除初始的 x,y 定位。这导致 x,y 坐标被应用两次,一次使用它们的原始值,一次使用它们相对于原点(而不是它们的起始位置)的拖动值。您应该为初始放置和拖动修改相同的 属性 - x、y 属性或变换,但不要将两者都用于放置。


D3v5

除了使用 d3.event.x 和 d3.event.y(d3.pointer 已添加到 d3v6 中),我还进行了上述更改,相信我已经创建了下面的预期效果。

var svg = d3.select("body")
  .append("svg")
  .attr("width", 500)
  .attr("height", 300);
  
d3.select("button")
  .on("click", function() {
  
   svg.append("rect")
      .attr("width",10)
      .attr("height", 25)
      .attr("x", 150)
      .attr("y", 100)
      .attr("fill","#1b1c3b")
      .attr("opacity", ".5")
      .attr("stroke", "black")
      .attr("stroke-width", "2")
      .attr("cursor", "move")
      .call(d3.drag()
        .on('start', dragStart)
        .on('drag', dragging)
        .on('end', dragEnd)
      )
  
    function dragStart(d,i,nodes){
        d3.select(nodes[i])
          .style("stroke", "red")  
      }
      
    function dragging(d,i,nodes){
        var xCoor = d3.event.x;
        var yCoor = d3.event.y;

        d3.select(nodes[i])
          .attr("x", xCoor)
          .attr("y", yCoor);
      }
      
      function dragEnd(d,i,nodes){
         d3.select(nodes[i])
          .style("stroke", "black")
      }
  
  
  
  })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<button>Add Rect</button>

D3v6

var svg = d3.select("body")
  .append("svg")
  .attr("width", 500)
  .attr("height", 300);
  
d3.select("button")
  .on("click", function() {
  
   svg.append("rect")
      .attr("width",10)
      .attr("height", 25)
      .attr("x", 150)
      .attr("y", 100)
      .attr("fill","#1b1c3b")
      .attr("opacity", ".5")
      .attr("stroke", "black")
      .attr("stroke-width", "2")
      .attr("cursor", "move")
      .call(d3.drag()
        .on('start', dragStart)
        .on('drag', dragging)
        .on('end', dragEnd)
      )
  
    function dragStart(event,d){
        d3.select(this)
          .style("stroke", "")  
      }
      
    function dragging(event,d){
        var xCoor = event.x;
        var yCoor = event.y;

        d3.select(this)
          .attr("x", xCoor)
          .attr("y", yCoor);
      }
      
      function dragEnd(event,d){
        d3.select(this)
          .style("stroke", "black")
      }
  
  })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.1.0/d3.min.js"></script>
<button>Add Rect</button>