Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.2k views
in Technique[技术] by (71.8m points)

javascript - D3 forceSimulation and dragging, what is node.fx/node.fy?

For d3 force layouts that include drag functionality with d3-drag, it seems that the functions called on each drag event modify d.fx/d.fy, eg:

function dragged(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

The drag start event often bases d.fx/d.fy on d.x/d.y while the end event sets d.fx/d.fy to null.

Where does d.fx/d.fy come from and why does it get used on elements that are being dragged? Is this built into d3 or d3-force in some way? Where is it assigned to the element being dragged?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

d3 force layout and node.fx/fy

Within a d3 force simulation, a node's fx/fy properties can be used to set a fixed position for that node. If the fx/fy values are undefined or null, the nodes is free to move around. If they are set, the x/y properties of the node will always be set to match the fx/fy properties:

At the end of each tick, after the application of any forces, a node with a defined node.fx has node.x reset to this value and node.vx set to zero; likewise, a node with a defined node.fy has node.y reset to this value and node.vy set to zero. To unfix a node that was previously fixed, set node.fx and node.fy to null, or delete these properties. (docs)

These fx/fy properties are used to fix nodes in general, not just during drag events.

Application to drag events in a d3 force layout:

In a d3 force simulation the position of each node is updated on every tick. The tick fires repeatedly throughout the simulation to keep the nodes position updated, it does so fast enough to appear to animate the nodes movement.

While dragging you want to keep the node's position where the mouse is. During a drag, each time the mouse is moved, the drag event fires. It doesn't fire continuously unless the mouse moves.

When dragging we don't want to apply a force to the node being dragged: we want the node to follow the mouse (we generally also don't want to freeze the rest of the nodes by stopping the simulation during drags).

In order to remove the effects of the force layout on the dragged node, we can set the node.fx/fy properties so that the force doesn't pull the nodes away from the mouse position. When the drag is complete, we want to unset (using null) those values so the force will position the node again.

In the snippet below two force layouts are presented. Each will behave differently:

  • In the red layout nodes have there fx/fy properties set to the mouse position during the drag.
  • In the blue layout nodes simply have their x/y properties set to the mouse position during the drag.

In the red layout the force won't re-position a node during a drag. In the blue layout the force will continue to act upon a node during a drag. In the blue example both drag and force continuously place the node based on their individual rules, though normally tick events will generally place the node frequently enough that a drag may not be very visible. Try dragging the blue node a bit then don't move the mouse - it'll drift according to the force layout only:

In both examples the drag functions still update the force layout regarding the position of the dragged node

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>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...