Skip to content

Force Layout

mbostock edited this page Jul 3, 2011 · 41 revisions

API ReferenceLayouts

A flexible force-directed graph layout implementation using position Verlet integration to allow simple constraints. For more on physical simulations, see Thomas Jakobsen. This implementation uses a quadtree to accelerate charge interaction using the Barnes–Hut approximation. In addition to the repulsive charge force, a psuedo-gravity force keeps nodes centered in the visible area and avoids expulsion of disconnected subgraphs, while links are fixed-distance geometric constraints. Additional custom forces and constraints may be applied on the "tick" event, simply by updating the x and y attributes of nodes.

force

Some fun examples:

Like other classes in D3, layouts follow the method chaining pattern where setter methods return the layout itself, allowing multiple setters to be invoked in a concise statement. Unlike some of the other layout implementations which are stateless, the force layout keeps a reference to the associated nodes and links internally; thus, a given force layout instance can only be used with a single dataset.

# d3.layout.force()

Constructs a new force-directed layout with the default settings: size 1×1, friction 0.9, distance 20, charge strength -30, gravity strength 0.1, and theta parameter 0.8. The default nodes and links are the empty array, and when the layout is started, the internal alpha cooling parameter is set to 0.1. The general pattern for constructing force-directed layouts is to set all the configuration properties, and then call start:

var force = d3.layout.force()
    .nodes(nodes)
    .links(links)
    .size([w, h])
    .start();

Note that, like D3's other layouts, the force-directed layout doesn't mandate a particular visual representation. Most commonly, nodes are mapped to SVG circle elements, and links are mapped to SVG line elements. But you might also display nodes as symbols or images.

# force.size([size])

If size is specified, sets the available layout size to the specified two-element array of numbers representing x and y. If size is not specified, returns the current size, which defaults to 1×1. The size affects two aspects of the force-directed layout: the gravitational center, and the initial random position. The center of gravity is simply [x / 2, y / 2]. When nodes are added to the force layout, if they do not have x and y attributes already set, then these attributes are initialized using a uniform random distribution in the range [0, x] and [0, y], respectively.

# force.distance([distance])

If distance is specified, sets the target distance between linked nodes to the specified value. If distance is not specified, returns the layout's current target distance, which defaults to 20. Typically, the distance is specified in pixels; however, the units are arbitrary relative to the layout's size. Links are not implemented as "spring forces", as in common in other force-directed layouts, but as weak geometric constraints. For each tick of the layout, the distance between each pair of linked nodes is computed and compared to the target distance; the links are then moved towards each other, or away from each other, so as to converge on the desired distance. This method of constraints relaxation on top of position Verlet integration is vastly more stable than previous methods using spring forces, and also allows for the flexible implementation of other constraints in the tick event listener, such as hierarchical layering.

The current implementation does not allow variable link distances or strengths; this is being tracked as issue #211 and will be added in a future release. The goal will be to allow the link distance and strength to be computed as a function on the link objects, allowing total flexibility in the link specification.

# force.friction([friction])

If friction is specified, sets the friction coefficient to the specified value. If friction is not specified, returns the current coefficient, which defaults to 0.9. The name of this parameter is perhaps misleading; it does not correspond to a standard physical coefficient of friction. Instead, it more closely approximates velocity decay: at each tick of the simulation, the particle velocity is scaled by the specified friction. Thus, a value of 1 corresponds to a frictionless environment, while a value of 0 freezes all particles in place. Values outside the range [0,1] are not recommended and may have destabilizing effects.

# force.charge([charge])

If charge is specified, sets the charge strength to the specified value. If charge is not specified, returns the current charge strength, which defaults to -30. A negative value results in node repulsion, while a positive value results in node attraction. For graph layout, negative values should be used; for n-body simulation, positive values can be used. All nodes are assumed to be infinitesimal points with equal charge and mass.

Unlike links, which only affect two linked nodes, the charge force is global: every node affects every other node, even if they are on disconnected subgraphs. To avoid quadratic performance slowdown for large graphs, the force layout uses the Barnes–Hut approximation which takes O(n log n) per tick. For each tick, a quadtree is created to store the current node positions; then for each node, the sum charge force of all other nodes on the given node are computed. For clusters of nodes that are far away, the charge force is approximated by treating the distance cluster of nodes as a single, larger node. The theta parameter determines the accuracy of the computation.

# force.gravity([gravity])

If gravity is specified, sets the gravitational strength to the specified value. If gravity is not specified, returns the current gravitational strength, which defaults to 0.1.

# force.theta([theta])

Get or set the accuracy of the charge interaction (Barnes–Hut approximation).

# force.nodes([nodes])

Get or set the array of nodes to layout.

# force.links([links])

Get or set the array of links between nodes.

# force.start()

Start the simulation. Can also be used to restart the simulation when the nodes or links change. As the layout stabilizes, it cools, slowing down movement and eventually stopping. This way, it doesn't hog the CPU or drain the battery.

# force.resume()

Reheat the cooling parameter (alpha) and restart simulation.

# force.stop()

Immediately terminate the simulation.

# force.on(type, listener)

Listen to "tick" updates in the computed layout positions. Use this to update the displayed nodes and links.

# force.drag()

Bind a behavior to nodes to allow interactive dragging. Use in conjunction with the call operator on the nodes.

Clone this wiki locally