about arbor

Arbor is a graph visualization library built with web workers and jQuery. Rather than trying to be an all-encompassing framework, arbor provides an efficient, force-directed layout algorithm plus abstractions for graph organization and screen refresh handling.

It leaves the actual screen-drawing to you. This means you can use it with canvas, SVG, or even positioned HTML elements; whatever display approach is appropriate for your project and your performance needs.

As a result, the code you write with it can be focused on the things that make your project unique – the graph data and your visual style – rather than spending time on the physics math that makes the layouts possible.

 

installation

To use the particle system, get jQuery and add the file at lib/arbor.js to your path somewhere and include them in your HTML:

<script src="path/to/jquery.min.js"></script>
<script src="path/to/arbor.js"></script>

If you want to let arbor handle realtime color and value tweens for you, include the arbor-tween.js file as well. this will add a pair of new tweening methods to the ParticleSystem object (see the docs to decide if this appeals to you or not).

<script src="path/to/jquery.min.js"></script>
<script src="path/to/arbor.js"></script>
<script src="path/to/arbor-tween.js"></script>

getting started

The source distribution contains a sample project that demonstrates some of the basic idioms for working with the library to build a visualization. More detailed documentation can be found in the reference section.

In addition, the demos folder of the source distribution contains standalone versions of the demos on the site. But since all of them use xhr to fetch their data, you'll still need to view them from an http server. If you don't have a copy of apache handy, use the demo-server.sh script to create a local server.

 

Contribute

Code submissions are greatly appreciated and highly encouraged. Please send pull requests with fixes, enhancements, etc. to samizdatco on github. The oldschool may also pipe their diff -u output to gro.sjrobra@ofni.

 

license

Arbor is released under the MIT license.

 

colophon

Arbor's design is heavily influenced by Jeffrey Bernstein's excellent Traer Physics library for Processing. In addition, much of the brute-force physics code was originally adapted from Dennis Hotson's springy.js.

The Barnes-Hut n-body implementation is based on Tom Ventimiglia and Kevin Wayne's vivid description of the algorithm. Thanks to all for such elegantly simple and comprehensible code.

ParticleSystem

The particle system stores your nodes and edges and handles updating their coordinates as the simulation progresses.

creation & use…

× creation

Parameters for the physics simulation can be set at creation-time by calling the constructor with the arguments:

arbor.ParticleSystem(repulsion, stiffness, friction, gravity, fps, dt, precision)

The parameters and their defaults are:

  • repulsion 1,000 the force repelling nodes from each other
  • stiffness 600 the rigidity of the edges
  • friction 0.5 the amount of damping in the system
  • gravity false an additional force attracting nodes to the origin
  • fps 55 frames per second
  • dt 0.02 timestep to use for stepping the simulation
  • precision 0.6 accuracy vs. speed in force calculations
       (zero is fast but jittery, one is smooth but cpu-intensive)

The defaults will be used for any omitted arguments. Parameters can also be passed in an object literal. For reference, the following calls are all equivalent:

arbor.ParticleSystem()
arbor.ParticleSystem(600)
arbor.ParticleSystem(600, 1000, .5, 55, .02, false)
arbor.ParticleSystem({friction:.5, stiffness:600, repulsion:1000})

Once a particle system has been created, the parameters can be tweaked by passing an object to the .parameters method:

var sys = arbor.ParticleSystem()
sys.parameters({gravity:true, dt:0.005})

rendering

The particle system doesn't do any drawing on its own; you need to provide those routines in a separate object that will be triggered by the system when it's time to redraw the screen. To set this up, create an object with two methods (.init and .redraw), then set the particle system's renderer attribute to your new object:

var myRenderer = {
  init:  function(system){ console.log("starting",system) },
  redraw:function(){ console.log("redraw") }
}
var sys = arbor.ParticleSystem()
sys.renderer = myRenderer

The .init method will be called once before the first pass through the draw loop. Then the .redraw method will be called each time the screen needs to be re-plotted. Take a look at the sample project for a slightly more elaborated example of how this works.

nodes

addNode(name, data)

name is a string identifier that will be used in talking to the particle system about this node.

data is an object with keys and values set by the user. you can use it to store additional information about the node for later use in e.g., drawing.

Creates a new node in the particle system and returns the resulting Node object.

getNode(name)

name is an identifier for a node already in the system

Returns the corresponding Node object or undefined if none is found. If called with a node as an argument, it will return that same node (for idempotence).

pruneNode(node)

node is either an identifier string or a Node object

Removes the corresponding Node from the particle system (as well as any Edges in which it is a participant).

edges

addEdge(source, target, data)

source and target are either identifier strings or a Node objects.

data is a user data object with additional information about the edge.

Creates a new edge connecting the source and target nodes then returns the resulting Edge object.

getEdges(source, target)

source and target are either identifier strings or a Node objects.

Returns an array containing all Edge objects connecting the specified nodes. If no connections exist, returns [].

getEdgesFrom(node)

node is a string identifier or Node object

Returns an array containing all Edge objects in which the node is the source. If no connections exist, returns [].

getEdgesTo(node)

node is a string identifier or Node object

Returns an array containing all Edge objects in which the node is the target. If no connections exist, returns [].

pruneEdge(edge)

edge is an Edge object.

Removes the corresponding Edge from the particle system.

iteration

eachNode(callback)

callback is a function with the signature ƒ(node, pt) where node is a Node object and pt is a Point object with its current location.

The callback function will be invoked once for each Node in the system.
Note that while the node.p attribute is always in the coordinate system of the particle system, the pt argument is transformed into pixel coordinates (provided you have called .screenSize to specify the screen bounding box).

eachEdge(callback)

callback is a function with the signature ƒ(edge, pt1, pt2) where edge is an Edge object and pt1 and pt2 are Point objects with the current endpoint locations.

The callback function will be invoked once for each Edge in the system.
Similar to the behavior of .eachNode, the edge.source.p and edge.target.p attributes are always in the coordinate system of the particle system, while pt1 and pt2 will be transformed into pixel coordinates (provided you have called .screenSize to specify the screen bounding box).

modification

graft(branch)

branch is an object of the form {nodes:{}, edges:{}}.

the nodes attribute contains a mapping of node names to data objects. For example,

{ nodes:{foo:{color:"red", mass:2},
         bar:{color:"green"}} }

the edges attribute contains nested objects to map source identifier to target, then target to edge data object. e.g,

{ edges:{bar:{foo:{similarity:0},
              baz:{similarity:.666}} }

Adds nodes and edges to the current set in the particle system. The leaf object values in the branch argument will be accessible through the .data attribute of the resulting Nodes and Edges.

merge(branch)

branch is an object of the form {nodes:{}, edges:{}} (see .graft for details).

Adds nodes and edges to the current set in the particle system and removes any that are not present in the new branch. Conserved nodes will maintain their position and state.

prune(callback)

callback is a function with the signature ƒ(node, from, to) where node is a Node object and from and to are arrays of edges for which node is the source and target respectively.

The callback function will be invoked once for each Node in the system and should return true if the node should be pruned or do nothing if the node should remain unaltered. Note that pruning a node will also remove all edges in which it participates

system settings

parameters( ) or (params)

if present, params is an object containing new settings values for the particle system. Valid keys are the same as for the ParticleSystem constructor function: repulsion, stiffness, friction, gravity, fps, and dt.

If called with no arguments, returns an object with the current system parameters as keys and values. If an argument is supplied, any values specified will be used as the new parameters (omitted values will remain unchanged).

fps( ) or (fps)

if present, the fps argument is a positive integer.

If called with no arguments, returns the frame rate achieved over the last few seconds of drawing. Otherwise the argument will set the new target frame rate. This affects the frequency with which the particle system iterates its simulation as well as the frequency with which the ParticleSystem calls the .redraw method of the object pointed to by its .renderer attribute.

bounds( )
Returns a bounding box containing all nodes using system coordinates. The return value is of the form:

{ topleft:{x:, y:}, bottomright:{x:, y:} }


energy( )
Returns some basic stats on the state of activity in the system. The values are in terms of velocity within the system's coordinate frame. This can be a useful measure of when the layout has stabilized. The return value is of the form:

{sum:, max:, mean:, n:}


start( )
Manually start the system running. By default the system will run and pause on its own based on the level of energy in the particles. You should only need to manually start after having previously called the .stop method.

stop( )
Pauses the particle simulation until .start is called. Since the system begins running as soon as it is supplied with nodes and edges, you may wish to call .stop shortly after creating the system object if it will not be displayed until later in the page lifetime (e.g., until a user action takes place).

coordinate helpers

screenSize(width, height)

width and height are positive integers defining the dimensions of the screen area you will be drawing in.

Calling this method enables automatic coordinate transformations from the particle system's coordinate system to your display's. This can be seen in the points supplied by the .eachNode and .eachEdge iterators as well as the to/fromScreen and nearest methods in this section.
You will nearly always want to call this once when setting up your ParticleSystem and renderer as well as whenever the dimensions of the display area change.

screenPadding(top, right, bottom, left)

All arguments are integers defining the number of pixels that should be left blank along each edge of the display area. Either 1, 2, or 4 arguments are expected and are interpreted similarly to the CSS padding: property.

When the system transforms points between coordinate systems it will factor the padding into the locations it provides to .eachNode and company.

screenStep(stepsize)

stepsize is a number between 0 and 1 defining the amount the bounding box should move from one frame to the next.

As the nodes move and the bounding box changes, the system applies a variable amount of smoothing to the 'camera' movements. As stepsize approaches 1 the amount of smoothing decreases as the bounds updates become more instantaneous.

screen() or (opts)

If present, opts is an object of the form:

{ size:{width:400, height:300},
  padding:[1,2,3,4],
  step:.1 }

This is a shorthand method combining the functions of the prior three. If called without an argument, returns the current screen size/padding/scaling. If an argument is supplied, updates the settings accordingly.

toScreen(systemPoint)

systemPoint is a Point object whose x and y values are set using the system's internal coordinate scheme.

Converts the x and y to screen coordinates on the basis of the current screen size and padding, returning the result as a new Point object. If the size hasn't been set or the system hasn't started yet, undefined will be returned instead.

fromScreen(screenPoint)

screenPoint is a Point object whose x and y values are using the screen's pixel coordinates.

Converts the x and y to system coordinates on the basis of the current screen size and padding, returning the result as a new Point object. If the size hasn't been set or the system hasn't started yet, undefined will be returned instead.

nearest(screenPoint)

screenPoint is a Point object whose x and y values are using the screen's pixel coordinates.

Returns a reference to the node nearest the argument's screen position in an object of the form:

{node:, point:, distance:}

node and point will either be the eponymous objects or null depending on whether any such node exists. distance is measured in pixels.

tweening

tweenNode(node, duration, opts)

node is a Node object or an identifier string for the node whose values you wish to tween.

duration is the time (in seconds) the transition should last.

opts is a mapping of names and target values.

This method allows you to initiate gradual transitions of values in the .data object of a given Node. For instance consider a node whose .data object looks like:

{color:"#00ff00", radius:1}

The following call will make this node quadruple in size and change color to light blue over the course of 3 seconds:

sys.tweenNode(myNode, 3, {color:"cyan", radius:4})

The system can handle tweening numerical values or colors which are expressed as either named CSS colors or hex strings beginning with a "#".
There is also a pair of 'magic' keys that can be included in the opts argument to modify the tween behavior. delay specifies the time in seconds before the tween should begin. ease can be set to one of the names seen in the src/easing.js file to control the dynamics of the transition.
!
Note that the tween methods are disabled unless you include the arbor-tween.js file in your page.

tweenEdge(edge, duration, opts)

edge is the Edge whose values you wish to tween.

duration is the time (in seconds) the transition should last.

opts is a mapping of names and target values.

Identical in behavior to .tweenNode except that it operates on the .data attribute of an Edge instead of a Node.
!
Note that the tween methods are disabled unless you include the arbor-tween.js file in your page.

datastructures

Node

Node objects encapsulate the current physics state of a point in the particle system as well as giving you a place to attach associated non-physics metadata.

creation & use…

× creation

New nodes are created through the particle system's .addNode method. For example:

sys = arbor.ParticleSystem()
node = sys.addNode("mynode", {mass:2, myColor:"goldenrod"})

This will create a new Node object with a .data field containing {myColor:"goldenrod"}. Note that the mass value was stripped out of the data object and used for the node's mass in the simulation.

The 'magic' variables you can use in this way (and their defaults):

  • mass 1.0 the node's resistance to movement and repulsive power
  • fixed false if true, the node will be unaffected by other particles
  • x auto the starting x position (in system coordinates)
  • y auto the starting y position (in system coordinates)

using nodes

With each tick of the simulation the values in .p will be updated based on the repulsion and spring forces in the system. To alter the node's properties (in response to, say, a mouse click), simply reset its values and the system will use the new values in its next tick:

console.log( node.p.x, node.p.y )
>> 1.2, 0.4

node.p = arbor.Point(1, 1)
console.log( node.p.x, node.p.y )
>> 1, 1

node.p.y = 13
console.log( node.p.x, node.p.y )
>> 1, 13

Each node contains an attribute called .data whose contents and use are entirely up to you. Typically it is used for storing metadata about the node so your rendering code can know how to draw it, what its label text should be, which url to go to on click, etc.

system values

name
String (read only)

mass
Number

fixed
Boolean

p
Point

user values

data
{ … }

Edge

Edge objects hold references to the source and target nodes they connect and have a preferred 'resting' length. They will apply forces on their endpoint nodes in an attempt to attain this optimal distance.

creation & use…

× creation

New edges are created through the particle system's .addEdge method. For example:

sys = arbor.ParticleSystem()
node1 = sys.addNode("one node")
node2 = sys.addNode("another")
edge = sys.addEdge(node1, node2, {length:.75, pointSize:3})

This creates a pair of Node objects then creates an Edge from the first to the second. The length key is a special variable that will be used for setting the edge's resting length. Any other keys in the object passed to .addEdge will be placed in the resulting Edge's .data attribute.

Note that .addEdge can be called with either actual Node objects or simply their .names as arguments. If a name is used but a node with that identifier does not yet exist, the system will automatically create one before creating the edge. For instance, the above code could be simplified to:

sys = arbor.ParticleSystem()
edge = sys.addEdge("one node", "another", {length:.75, pointSize:3})

system values

source
Node

target
Node

length
Number

user values

data
{ … }

Point

Point objects are simple containers for x/y coordinates bundled together with handy methods for doing vector calculations. Create points by calling arbor.Point(x, y).

coordinate data

x
Number

y
Number

vector math

add(pt) → Point
Returns a new Point with the sum of the two points.
subtract(pt) → Point
Returns a new Point with the difference of the two points.
multiply(n) → Point
Returns a linearly scaled copy of the original point.
divide(n) → Point
Returns a linearly scaled copy of the original point.
magnitude( ) → Number
Returns the point's distance from the origin.
normal( ) → Point
Returns the point's vector normal.
normalize( ) → Point
Returns a scaled copy of the point with a magnitude of one.

sanity checking

exploded( ) → Boolean
Returns true if x or y is NaN.
CC MI.MO 2014