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

后端 未结 1 628
余生分开走
余生分开走 2020-12-20 10:11

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:

相关标签:
1条回答
  • 2020-12-20 10:51

    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>

    The d in the drag functions being an individual node in the nodes data array (the node being dragged), from which the force layout bases its calculations and where it updates positions

    Also, some drag started events may use d.fx = d.x, this will simply set the node's position to its current position (as I do above), you could also use the mouse's current position without any noticeable difference.

    0 讨论(0)
提交回复
热议问题