score:4

Accepted answer

Dirty fix

Change this...

.friction(0.9)

to this...

.friction(0)

Background

Inside the force layout module in d3, there is a force.tick method that is called before every animation frame. This is where the node positions are recalculated. There is a calculation for links which takes into account link strength, weights and the target linkDistance; a gravity calculation which is a function of the distance of each node from the center of the layout; and a charge calculation which is based on the charge, the chargeDistance and the relative position of the nodes. There is also a calculation for friction. All of these - except for the friction calculation - take into account the current "temperature" of the layout (alpha), which is really just an exponentially decaying value that is a function of how many ticks have elapsed since the layout was started.

These calculations are sequentially applied to all of the elements of the layout, the input positions for each step being the output of the previous. But "fixed" nodes are treated differently for the friction calculation and nodes being dragged are fixed by the drag behavior.

friction is not really "friction", it's more like velocity decay as explained in the WIKI, The friction calculation is meant to maintain the velocity of the nodes by moving them away from their position at the end of the previous tick (px, py). The distance moved away is proportional to the velocity of each node which is based on the distance between (px, py) and the position calculated by the previous steps (links, charge and gravity) in the current tick (in reality it's a little more complicated than that because the charge calculation is actually "non-causal" and changes the previous positions, but this doesn't affect the principle of the friction calculation).

During the drag, (px, py) is updated with the mouse position by the drag behavior on every mousemove. Then, on the next tick, these values are copied to (x, y) in the force layout. So, during the drag, the previous position is actually the current position and vice versa! Therefore, when the drag ends, the velocity used in the friction calculation is in the opposite direction to it's actual velocity, so the friction calc tries to maintain this... and that's why it jumps back.

Dead stop

My next move would be to find a way to set (px, py) to (x, y) in the dragend event handler.
Like this for example...

  var stdDragEnd = force.drag().on("dragend.force");
  force.drag().on("dragend.force", myDragEnd);
  function myDragEnd(d) {
    d.px = d.x; d.py = d.y;
    stdDragEnd.call(this, d)
  }

You can put this anywhere in the code where your force variable is already defined. The idea is to hook the standard behaviour rather than replace it.

Now, even if you set friction to 1.0, the node will stop dead after the dragend.
You don't need to preserve the this context based on the current state of the code by the way, but anyway, it's good practice I guess. Who knows what the future will bring lah ;)


Related Query

More Query from same tag