我有一个 SVG 有一些组和拖动行为。它工作得很好,但是当我拖动元素两次时,它们会从“初始”位置重新开始。我需要新鲜的眼睛来解决这个问题!
示例 : jsfiddle
还有一个片段:
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])+")")
编辑
我尝试更新 停止函数将起始值更新为
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
但盒子总是在初始位置开始。我糊涂了
谢谢你的帮助!
最佳答案
您正在通过变换和 x/y 属性进行定位,这可能会导致在查看问题时遇到一些困难。更重要的是,问题是基于索引的定位和基于基准的定位的结合。
问题
如果删除 g
上的初始转换:
"translate(" + (10+i*size+10*i) + "," + 10 + ")";
你得到每组元素/
g
没有变换:矩形 A 锚定在 50,50,矩形 B 锚定在 [150,150](使用 x,y 属性设置)。线条和文本也基于 anchor 以类似方式定位。上图中每个
g
的转换为 [0,0] .现在让我们看看你的拖动主题:
.subject(function() {
var t = d3.select(this);
return {x: t.attr("x"), y: t.attr("y")};
})
我会注意到
g
s(this
上面)没有 x 或 y 属性(数据确实有 x 和 y 属性,但是您传递的是 HTML 元素的 x 和 y 属性)。写作 return {x:null,y:null}
产生相同的结果D3 可以通过将主题视为 [0,0] 来处理此拖动主题。因此,所有阻力都相对于 [0,0],这意味着
g
的所有阻力元素是相对于上图的,无论最后一次拖到哪里,g
:一个像素的拖动将其中一个框从上图中的位置移动一个像素,而不是从拖动开始时的任何位置。解决问题
由于您定位数据的方式,这有点棘手。您设置了一个独立于绑定(bind)数据但依赖于索引的初始翻译。然后,绑定(bind)数据用于在此之上应用 x,y 属性。通常我们会简单地使用绑定(bind)数据来表示有关元素位置的所有内容。
让我们使用 example 中的模式您首先在评论中链接到,然后对其进行调整。
此模式使用拖动的默认主题:
function subject(d) {
return d == null ? {x: d3.event.x, y: d3.event.y} : d;
}
如果
d
已定义,就是这样,我们使用被拖动元素的绑定(bind)基准作为使用 x 和 y 属性进行拖动的引用点。然后,在拖动过程中,我们更新基准以反射(reflect)其新位置,如下所示: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)
})
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
但是,我们在使用您的代码时遇到了问题:
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 模式:绑定(bind)数据不用于定位表示数据的元素。让我们改变它。让我们使用以下数据结构:
{
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 来设置矩形的 anchor (矩形的 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>
关于javascript - D3.js v4 元素在拖动后会回到初始位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58728054/