【问题标题】:D3.js v4 elements are going back to initial position after draggingD3.js v4 元素在拖动后会回到初始位置
【发布时间】:2019-11-06 10:31:04
【问题描述】:

我有一个包含一些组和拖动行为的 SVG。它工作得很好,但是当我两次拖动元素时,它们会从“初始”位置重新开始。我需要新鲜的眼光来解决这个问题!

示例jsfiddle

还有一个sn-p:

var items = [{name:"A", x:50, y:50}, {name:"B", x:150, y:50}];
var size = 96;

var svg = d3.select("#container")
              .append("svg");
              
 var items = svg.selectAll("g")
 								.data(items)
                .enter()
                		.append("g")
                    .attr("transform", function(d, i){
                    		return "translate(" + (10+i*size+10*i) + "," + 10 + ")";
                    });
                  
                    
     items.call(d3.drag()
    	 // .subject(function(d) { return d; })
       // .on("start", dragstarted)
          .on("drag", draggedGroup)
          .subject(function() { 
       				 var t = d3.select(this);
       				 return {x: t.attr("x"), y: t.attr("y")};
    })
        //.on("end", dragended)
      );
                    
                    
    items
    		 .append("rect")
         .attr("x",function(d){return d.x;})
         .attr("y",function(d){return d.y;})
         .attr("height",size)
         .attr("width", size)
         .style("stroke", "#0F0")
         .style("fill", "transparent")
         .style("stroke-width", 3.5);
         
    items.append("line")
    			.style("stroke", "#0FF")
         .attr("x1", function(d){return d.x;})
         .attr("y1", function(d){return d.y;})
         .attr("x2", function(d){return d.x+size;})
         .attr("y2", function(d){return d.y+size;})
         
     items.append("line")
    		 .style("stroke", "#0FF")
         .attr("x1", function(d){return d.x+size;})
         .attr("y1", function(d){return d.y;})
         .attr("x2", function(d){return d.x;})
         .attr("y2", function(d){return d.y+size;})
         
    items .append("text")
     			.attr("x", function(d){return d.x+size/2})
     			.attr("y", function(d){return d.y+size/2})
       							//.attr("text-anchor", "start")
       		.style("fill", "white")
       		.text(function(d){
          		return d.name;
           })
      
         
function dragstarted(d){
	console.log("Moving "+d.name);
//  d3.select(this).raise().classed("active", true);
} 

function draggedGroup(d, i, a){

		const pos   = [d3.event.x , d3.event.y];
		const pdist = [d3.event.dx , d3.event.dy];
		d3.select(this).attr("transform", "translate("+pos[0]+","+pos[1]+")")
    
} 

function dragended(d){
 // d3.select(this).raise().classed("active", false);
  console.log("Stop moving "+d.name)
} 
body{
  background-color:black;
  padding:10px;
}

svg{
  
   width:500px;
  height:400px;
}

#container{
  width:500px;
  height:400px;
  background-color: #00F;
  
  
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="container"></div>

我的分组项:

var items = svg.selectAll("g")
                .data(items)
                .enter()
                    .append("g")
                    .attr("transform", function(d, i){
                        return "translate(" + d.x + "," + d.y + ")";
                    });


items.append("rect")
     .attr("x",function(d){return d.x;})
     .attr("y",function(d){return d.y;})
     .attr("height",size)
     .attr("width", size)
     .style("stroke", "#0F0")
     .style("fill", "transparent")
     .style("stroke-width", 3.5);

items.append("line")
            .style("stroke", "#0FF")
     .attr("x1", function(d){return d.x;})
     .attr("y1", function(d){return d.y;})
     .attr("x2", function(d){return d.x+size;})
     .attr("y2", function(d){return d.y+size;})

 items.append("line")
         .style("stroke", "#0FF")
     .attr("x1", function(d){return d.x+size;})
     .attr("y1", function(d){return d.y;})
     .attr("x2", function(d){return d.x;})
     .attr("y2", function(d){return d.y+size;})

items .append("text")
            .attr("x", function(d){return d.x+size/2})
            .attr("y", function(d){return d.y+size/2})
                            //.attr("text-anchor", "start")
        .style("fill", "white")
        .text(function(d){
            return d.name;
       })

行为

   items.call(d3.drag()
      .on("drag", draggedGroup)
      .subject(function() { 
         var t = d3.select(this);
        return {x: t.attr("x"), y: t.attr("y")};
       })
  );

拖动功能

function draggedGroup(d, i, a){
    const pos   = [d3.event.x , d3.event.y];
    d3.select(this).attr("transform", "translate("+pos[0]+","+pos[1]+")")  
 }

为什么当我第二次尝试拖动元素时它从第一个位置重新开始?请参阅 jsfiddle 上的示例以查看实际操作。

错误似乎在这行拖拽函数中:

 d3.select(this).attr("transform", "translate("+(pos[0])+","+(pos[1])+")")

编辑

我尝试更新 STOP 函数以将起始值更新为

    d3.select(this).attr("transform", function(d){

             d.x += d3.event.x;
             d.y += d3.event.y;

        return "translate("+d3.event.x+","+d3.event.y+")"
})

现在,如果我在拖动开始时将 d.x, d.y 放在控制台中,输出会显示

x: 640 y: 299

但盒子总是从初始位置开始。我很困惑

感谢您的帮助!

【问题讨论】:

  • 之所以总是从初始位置开始是因为初始位置永远不会改变。 xy 永远不会改变。您只需使用翻译转换位置,但每次您开始拖动时,转换都会从初始的xy 开始。
  • 是的。这是对的。如何解决?
  • 我已经根据小提琴中的代码进行了回答,这与问题中的代码不同(g 元素的初始转换)。我已经将代码从小提琴带到了问题中,因为它是我为答案构建的。不过,我看到您编辑了问题以删除这部分代码。如果我的回答解决了您的问题,为了保持一致性,我可能会对其进行编辑。主题和初始转换是我在下面解决的问题的两个部分。

标签: javascript d3.js


【解决方案1】:

您正在通过转换和 x/y 属性进行定位,这可能会导致在查看问题时遇到一些困难。更重要的是,问题是基于索引的定位和基于基准的定位的结合。

问题

如果您删除g 上的初始转换:

"translate(" + (10+i*size+10*i) + "," + 10 + ")";

您无需转换即可获得每组元素/g

矩形 A 锚定在 50,50,矩形 B 锚定在 [150,150](使用 x,y 属性设置)。线条和文本也基于锚点以类似方式定位。上图中每个g 的翻译是[0,0]。

现在让我们看看你的拖动主题:

     .subject(function() { 
        var t = d3.select(this);
        return {x: t.attr("x"), y: t.attr("y")};
     })

我会注意到gs(上面的this)没有 x 或 y 属性(数据确实有 x 和 y 属性,但您传递的是 HTML 元素的 x 和 y属性代替)。写return {x:null,y:null} 会产生相同的结果

D3 可以通过将主题视为 [0,0] 来处理此拖动主题。因此,所有拖动都相对于 [0,0],这意味着 g 元素的所有拖动都相对于上图,无论最后一次拖动离开 g 的位置:一个像素的拖动移动一个像素从上图中的位置开始框一个像素,而不是从拖动开始时的任何位置。

解决问题

这有点棘手,因为您是如何定位数据的。您已经设置了一个独立于绑定数据但依赖于索引的初始翻译。然后,绑定数据用于在此之上应用 x,y 属性。通常我们会简单地使用绑定数据来表示关于元素位置的所有内容。

让我们先使用您在 cmets 中链接到的 example 中的模式,然后对其进行调整。

此模式使用拖拽的默认主题:

function subject(d) {
  return d == null ? {x: d3.event.x, y: d3.event.y} : d;
}

如果定义了d,我们使用被拖动元素的绑定基准作为参考点,使用x 和y 属性进行拖动。然后,在拖动过程中,我们更新基准以反映其新位置,如下所示:

function dragged(d) {
  d3.select(this)
    .attr("x", d.x = d3.event.x)
    .attr("y", d.y = d3.event.y); 
}

这看起来像:

var svg = d3.select("body")
  .append("svg")
  .attr("width",500)
  .attr("height", 300);
  
var rect = svg.append("rect")
  .datum({x:50,y:50})
  .attr("width", 50)
  .attr("height", 50)
  .attr("x", function(d) { return d.x; })
  .attr("y", function(d) { return d.y; })
  .call(d3.drag()
    .on("drag", function(d) {
      d3.select(this)
        .attr("x", d.x = d3.event.x)
        .attr("y", d.y = d3.event.y)
    })  
  )
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"&gt;&lt;/script&gt;

但是,我们在使用您的代码时遇到了问题:

var items = [{name:"A", x:50, y:50}, {name:"B", x:150, y:50}];
var size = 96;

var svg = d3.select("#container")
   .append("svg");
              
var items = svg.selectAll("g")
 	.data(items)
  .enter()
  .append("g")
  .attr("transform", function(d, i){
     return "translate(" + (10+i*size+10*i) + "," + 10 + ")";
  });
                  
                    
items.call(d3.drag()
  .on("drag", draggedGroup)
  )
                    
items
   .append("rect")
   .attr("x",function(d){return d.x;})
   .attr("y",function(d){return d.y;})
   .attr("height",size)
   .attr("width", size)
   .style("stroke", "#0F0")
   .style("fill", "transparent")
   .style("stroke-width", 3.5);
         
items.append("line")
   .style("stroke", "#0FF")
   .attr("x1", function(d){return d.x;})
   .attr("y1", function(d){return d.y;})
   .attr("x2", function(d){return d.x+size;})
   .attr("y2", function(d){return d.y+size;})
         
items.append("line")
   .style("stroke", "#0FF")
   .attr("x1", function(d){return d.x+size;})
   .attr("y1", function(d){return d.y;})
   .attr("x2", function(d){return d.x;})
   .attr("y2", function(d){return d.y+size;})
         
items .append("text")
   .attr("x", function(d){return d.x+size/2})
   .attr("y", function(d){return d.y+size/2})
   .style("fill", "white")
   .text(function(d){
      return d.name;
   })

function draggedGroup(d, i, a){
    d.x = d3.event.x;
    d.y = d3.event.y;
		d3.select(this).attr("transform", "translate("+d.x+","+d.y+")")
    
}
body{
  background-color:black;
  padding:10px;
}

svg{
  
   width:500px;
  height:400px;
}

#container{
  width:500px;
  height:400px;
  background-color: #00F;
  
  
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="container"></div>

除了第一次拖动时的初始跳跃外,一切正常。初始拖动跳跃,因为拖动平移是相对于 d.x 和 d.y,但是 g 元素是使用不基于 d.x 或 d.y 的初始平移绘制的,它是:"translate(" + (10+i*size+10*i) + "," + 10 + ")"

这使得采用 D3 模式变得困难:绑定数据不用于定位表示数据的元素。让我们改变它。让我们使用以下数据结构:

{
  name: same as before,
  x: drag position X,
  y: drag position Y,
  x1: anchor point X of rectangle, // currently d.x
  y1: anchor point Y of rectangle  // currently d.y
}

当然我们需要更新 d.x/d.y 的每一个引用

现在我们可以使用数据的 x,y 属性来跟踪拖动平移。我们将这些设置为上面翻译中使用的初始值。而我们使用 x1 和 y1 来设置矩形的锚点(矩形的 x/y 属性):

 var items = [{name:"A", x1:50, y1:50}, {name:"B", x1:150, y1:50}];
 var size = 96;

 items.forEach(function(d,i) {
    d.x = 10+i*size+10*i;
    d.y = 10;
 })

现在我们可以使用 d.x/d.y 设置初始平移,使用默认拖动主题,并在每个拖动事件上更新 d.x 和 d.y。一切都应该是花花公子:

var items = [{name:"A", x1:50, y1:50}, {name:"B", x1:150, y1:50}];
var size = 96;

items.forEach(function(d,i) {
    d.x = 10+i*size+10*i;
    d.y = 10;
})

var svg = d3.select("#container")
   .append("svg");
              
var items = svg.selectAll("g")
 	.data(items)
  .enter()
  .append("g")
  .attr("transform", function(d, i){
    return "translate(" + d.x + "," + d.y + ")";
  });
                    
                  
                    
items.call(d3.drag()
  .on("drag", draggedGroup)
  )
                    
items
   .append("rect")
   .attr("x",function(d){return d.x1;})
   .attr("y",function(d){return d.y1;})
   .attr("height",size)
   .attr("width", size)
   .style("stroke", "#0F0")
   .style("fill", "transparent")
   .style("stroke-width", 3.5);
         
items.append("line")
   .style("stroke", "#0FF")
   .attr("x1", function(d){return d.x1;})
   .attr("y1", function(d){return d.y1;})
   .attr("x2", function(d){return d.x1+size;})
   .attr("y2", function(d){return d.y1+size;})
         
items.append("line")
   .style("stroke", "#0FF")
   .attr("x1", function(d){return d.x1+size;})
   .attr("y1", function(d){return d.y1;})
   .attr("x2", function(d){return d.x1;})
   .attr("y2", function(d){return d.y1+size;})
         
items .append("text")
   .attr("x", function(d){return d.x1+size/2})
   .attr("y", function(d){return d.y1+size/2})
   .style("fill", "white")
   .text(function(d){
      return d.name;
   })

function draggedGroup(d, i, a){
    d.x = d3.event.x 
    d.y = d3.event.y
		d3.select(this).attr("transform", "translate("+d.x+","+d.y+")")
    
}
body{
  background-color:black;
  padding:10px;
}

svg{
  
   width:500px;
  height:400px;
}

#container{
  width:500px;
  height:400px;
  background-color: #00F;
  
  
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="container"></div>

【讨论】:

  • 安德鲁!惊人的答案!感谢您修复并非常感谢您解释该问题!我真的很感激!我喜欢 d3,但我不经常使用它,所以当我需要它时,我会遇到麻烦!我看到了你的伟大工作!非常好!!!我将使用 lichens.js!绝对地!我知道我不应该在这里用 cmets 表示感谢,但是……今天我必须向某人提供啤酒! :D 干得好,伙计!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-06
  • 2018-10-02
  • 1970-01-01
相关资源
最近更新 更多