以下代码基于Bostock的Circle Dragging I。
原始代码在拖动圆圈时更新(MVC)模型。函数d
的参数dragged
是被拖动的数组circles
的元素。
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.active {
stroke: #000;
stroke-width: 2px;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
radius = 32;
var circles = [
{x: 100, y: 100},
{x: 100, y: 200},
{x: 100, y: 300}
];
var lastIndex = 2;
var color = d3.scaleOrdinal()
.range(d3.schemeCategory20);
svg.selectAll("circle")
.data(circles)
.enter().append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", radius)
.style("fill", function(d, i) { return color(i); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("active", false);
}
//----------------Addition----------------
var click = function() {
if (d3.event.defaultPrevented) return;
var p = d3.mouse(this),
x = p[0],
y = p[1];
circles[++lastIndex] = {x: x, y: y};
svg.append('circle')
.attr('cx', x)
.attr('cy', y)
.attr('r', radius)
.style("fill", function(d, i) { return color(lastIndex); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
};
svg.on('click', click);
</script>
在修改中(在“添加”下),函数click
可以添加新的圆圈,但拖动行为对新点不起作用,可能是因为除非通过svg.data(circles).enter.append('circle')...
添加圆圈,D3.js不会将SVG圈中的引用保留回circles
数组中的条目。
解决方案是将svg.data(circles).enter.append('circle')...
置于函数refresh()
中,并在将新圆圈插入数组refresh()
后调用circles
。
这样做意味着当数组circles
具有n
个元素时,会在插入n
个元素时遍历(n+1
个步骤)。< / p>
是否可以在启用拖动行为的情况下插入新圆圈,并在拖动过程中保持模型和视图同步,而不必在插入每个新圆圈时遍历circles
?
答案 0 :(得分:3)
只需设置每个新圈子的datum
即可。这样,D3可以在拖动时设置d.x
和d.y
:
svg.append('circle')
.datum(circles[lastIndex])
//etc...
以下是仅包含该更改的更新代码:http://blockbuilder.org/anonymous/6dbf734683e9e8e485a1cda46978b58e
这是S.O.使用相同代码的代码段:
.active {
stroke: #000;
stroke-width: 2px;
}
<svg width="500" height="400"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
radius = 32;
var circles = d3.range(20).map(function() {
return {
x: Math.round(Math.random() * (width - radius * 2) + radius),
y: Math.round(Math.random() * (height - radius * 2) + radius)
};
});
var color = d3.scaleOrdinal()
.range(d3.schemeCategory20);
svg.selectAll("circle")
.data(circles)
.enter().append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", radius)
.style("fill", function(d, i) { return color(i); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("active", false);
}
var lastIndex = 0;
var click = function() {
if (d3.event.defaultPrevented) return;
var p = d3.mouse(this),
x = p[0],
y = p[1];
circles[++lastIndex] = {x: x, y: y};
svg.append('circle')
.datum(circles[lastIndex])
.attr('cx', x)
.attr('cy', y)
.attr('r', radius)
.style("fill", function(d, i) { return color(lastIndex); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
};
svg.on('click', click);
</script>
更简单的替代方法就是更改dragged
函数,因此它不依赖于任何绑定数据:
function dragged() {
d3.select(this).attr("cx", d3.event.x).attr("cy", d3.event.y);
}
这似乎不仅是最简单的解决方案,也是最好的解决方案。正如@ his answer中的@altocumulus所指出的那样:
由于SVG DOM隐含地在DOM中携带定位信息,因此无需在模型中明确复制此信息,即在绑定到DOM元素的数据中。
答案 1 :(得分:1)
这是Gerardo Furtado正确answer的附录,不是替代品,也不是替代品。我决定提供更多信息,并分享我对此的看法。
正如Gerardo指出的那样,你的假设是,对于新添加的圈子,拖动行为不起作用是错误的。拖动行为已成功附加,即使对于新圈子也是如此。但是,dragged()
函数会中断,因为它会尝试访问并分配绑定到拖动圆的数据。由于新添加的圈子没有绑定到它们的数据,因此会导致错误,导致拖动行为不适用于这些圈子。正如Gerardo所建议的,通过使用.datum()
将数据绑定到新的圈子或者在拖动处理函数中删除数据访问,有两种方法。
尽管两种方法都有效,但我更喜欢后者,因为它简单。如果除了跟踪圈子的位置之外,您不需要将数据用于任何其他目的,则无需使此模型保持最新状态。由于SVG DOM隐含地在DOM中携带定位信息,因此无需在模型中明确复制此信息,即在绑定到DOM元素的数据中。
在绘制<canvas>
元素时,您肯定需要使用模型来跟踪位置变化。画布更多地遵循 draw-and-forget 方法,并且不受分层DOM的支持。与SVG相比,它没有内置的独特可选元素概念,因此缺乏对数据绑定到元素的支持。为了能够跟踪您的元素,您必须自己提供模型支持此视图。
我认为,记住这种差异,可以更深入地了解数据更新进入链接示例的原因,尽管Mike Bostock以其相当简洁的代码而闻名。但是块Circle Dragging I也链接到Circle Dragging II,它使用画布执行相同的操作。注意到两个块都是在同一天设置的,SVG演示,即 Circle Dragging I ,似乎已经从画布示例中遗留了一些剩余代码。