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.
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>
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.
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.
Arbor is released under the MIT license.
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.
The particle system stores your nodes and edges and handles updating their coordinates as the simulation progresses.
creation & use…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:
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})
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.
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.
Node object.name is an identifier for a node already in the system
Node object or
undefined if none is found. If called with a node as an
argument, it will return that same node (for idempotence).node is either an identifier string or a Node object
Node from the
particle system (as well as any Edges in which it is a participant).source and target are either identifier strings or a Node objects.
data is a user data object with additional information about the edge.
Edge object.
source and target are either identifier strings or a Node objects.
Edge objects connecting the specified nodes.
If no connections exist, returns [].
node is a string identifier or Node object
Edge objects in which the
node is the source.
If no connections exist, returns [].
node is a string identifier or Node object
Edge objects in which the
node is the target.
If no connections exist, returns [].
edge is an Edge object.
Edge from the particle system.
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.
Node in the system.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).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.
Edge in the system..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).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}} }
branch argument will be accessible through the
.data attribute of the resulting Nodes and Edges.
branch is an object of the form {nodes:{}, edges:{}}
(see .graft for details).
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.
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 participatesif 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 present, the fps argument is a positive integer.
ParticleSystem calls the
.redraw method of the object pointed to by its
.renderer attribute.
{ topleft:{x:, y:}, bottomright:{x:, y:} }
{sum:, max:, mean:, n:}
.stop method.
.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).
width and height are positive integers defining the
dimensions of the screen area you will be drawing in.
.eachNode and .eachEdge
iterators as well as the to/fromScreen and nearest
methods in this section.
ParticleSystem and renderer as well as whenever the dimensions
of the display area change.
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.
.eachNode and company.
stepsize is a number between 0 and 1 defining the
amount the bounding box should move from one frame to the next.
If present, opts is an object of the form:
{ size:{width:400, height:300},
padding:[1,2,3,4],
step:.1 }
systemPoint is a Point object whose x and y
values are set using the system’s internal coordinate scheme.
Point object. If the
size hasn’t been set or the system hasn’t started yet, undefined will be returned
instead.
screenPoint is a Point object whose x and y
values are using the screen’s pixel coordinates.
Point object. If the
size hasn’t been set or the system hasn’t started yet, undefined will be returned
instead.
screenPoint is a Point object whose x and y
values are using the screen’s pixel coordinates.
{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.
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.
.data
object of a given Node. For instance consider a node whose .data
object looks like:
{color:"#00ff00", radius:1}
sys.tweenNode(myNode, 3, {color:"cyan", radius:4})
#”.
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.
arbor-tween.js
file in your page.
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.
.tweenNode except that it operates on the .data
attribute of an Edge instead of a Node.
arbor-tween.js
file in your page.
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…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):
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.
{ … }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…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})
{ … }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).
true if x or y is NaN.