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 Edge
s 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 Node
s and Edge
s.
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 .name
s 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.