对于包含带有d3-drag拖动功能的d3强制布局,似乎每个拖动事件上调用的功能都会修改d.fx
/ d.fy
,例如:
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
拖动开始事件通常将d.fx
/ d.fy
基于d.x
/ d.y
,而结束事件将d.fx
/ d.fy
设置为{ {1}}。
null
/ d.fx
来自何处,为什么在要拖动的元素上使用它?这是以某种方式内置到d3或d3力中吗?它在哪里分配给要拖动的元素?
答案 0 :(得分:3)
node.fx
/ fy
在d3力模拟中,节点的fx / fy属性可用于为该节点设置固定位置。如果fx / fy值未定义或为null,则节点可以自由移动。如果已设置,则节点的x / y属性将始终设置为与fx / fy属性匹配:
在每个刻度线的结尾,在施加任何力后,一个节点 使用已定义的node.fx将node.x重置为此值并设置node.vx 归零;同样,具有已定义的node.fy的节点将node.y重置为 此值和node.vy设置为零。取消修复以前的节点 先前已修复,请将node.fx和node.fy设置为null,或删除它们 属性。 (docs)
这些fx / fy属性通常用于fix nodes,而不仅仅是在拖动事件期间。
在d3力模拟中,每个节点的位置在每个刻度上都会更新。在整个仿真过程中,滴答声会反复触发,以保持节点位置更新,其速度如此之快,以至于似乎可以使节点运动动起来。
在拖动时,您希望将节点的位置保持在鼠标所在的位置。在拖动过程中,每次移动鼠标时,都会触发拖动事件。除非鼠标移动,否则它不会连续触发。
拖动时,我们不想对要拖动的节点施加力:我们希望节点跟随鼠标(我们通常也不想通过停止模拟来冻结其余节点在拖动过程中)。
为了消除力布局对拖动节点的影响,我们可以设置node.fx
/ fy
属性,以使力不会将节点拉离鼠标位置。拖动完成后,我们要取消设置(使用null
)这些值,以便用力再次将节点定位。
在下面的代码段中,给出了两个力的布局。每个都会有不同的行为:
fx
/ fy
属性在拖动期间设置为鼠标位置。 x
/ y
属性设置为拖动期间的鼠标位置。在红色布局中,在拖动过程中力不会重新定位节点。在蓝色布局中,在拖动过程中,力将继续作用在节点上。在蓝色示例中,拖动和强制都基于其各自的规则连续放置节点,尽管通常滴答事件通常会足够频繁地放置节点,以至于拖动可能不太明显。尝试稍微拖动蓝色节点,然后不要移动鼠标-它只会根据力的布局而漂移:
在两个示例中,拖动功能仍会更新有关被拖动节点位置的力布局
var data1 ={ "nodes": [{"id": "A"},{"id": "B"},{"id": "C"},{"id":"D"}], "links": [{"source": "A", "target": "B"}, {"source": "B", "target": "C"}, {"source": "C", "target": "A"}, {"source": "D", "target": "A"}] }
var data2 ={ "nodes": [{"id": "A"},{"id": "B"},{"id": "C"},{"id":"D"}], "links": [{"source": "A", "target": "B"}, {"source": "B", "target": "C"}, {"source": "C", "target": "A"}, {"source": "D", "target": "A"}] }
var height = 250; var width = 400;
var svg = d3.select("body").append("svg")
.attr("width",width)
.attr("height",height);
// FIRST SIMULATION
var simulation1 = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(50))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 3, height / 2));
var link1 = svg.append("g")
.selectAll("line")
.data(data1.links)
.enter().append("line")
.attr("stroke","black");
var node1 = svg.append("g")
.selectAll("circle")
.data(data1.nodes)
.enter().append("circle")
.attr("r", 10)
.call(d3.drag()
.on("drag", dragged1)
.on("end", dragended1))
.attr("fill","crimson");
simulation1.nodes(data1.nodes)
.on("tick", ticked1)
.alphaDecay(0)
.force("link")
.links(data1.links);
function ticked1() {
link1
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node1
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function dragged1(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended1(d) {
d.fx = null;
d.fy = null;
}
// SECOND SIMULATION
var simulation2 = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(50))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width * 2 / 3, height / 2));
var link2 = svg.append("g")
.selectAll("line")
.data(data2.links)
.enter().append("line")
.attr("stroke","black");
var node2 = svg.append("g")
.selectAll("circle")
.data(data2.nodes)
.enter().append("circle")
.attr("r", 10)
.call(d3.drag()
.on("drag", dragged2))
.attr("fill","steelblue");
simulation2.nodes(data2.nodes)
.on("tick", ticked2)
.alphaDecay(0)
.force("link")
.links(data2.links);
function ticked2() {
link2
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node2
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function dragged2(d) {
d.x = d3.event.x;
d.y = d3.event.y;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
拖动功能中的d
是节点数据数组(要拖动的节点)中的单个节点,力布局从该节点开始计算并在其中更新位置 < / p>
此外,某些拖动启动事件可能会使用d.fx = d.x
,这将简单地将节点的位置设置为其当前位置(如上所述),您也可以使用鼠标的当前位置而没有明显的差异。 >