Layouts

The Surface component in the Toolkit has support for layouts for the nodes in your UI. The Toolkit ships with support
for four layout types, and exposes an API for you to write your own.

You do not interact directly with a Layout when using the Toolkit: the methods you generally need to use are exposed
through the Surface class.

Every Layout supports the concept of "magnetization" - adjusting the positions of elements such that no two elements
overlap. This is a useful mechanism for making small adjustments to the UI without running a layout all over again. You
can also apply layouts in an 'adhoc' fashion - just once, without changing the main layout.

... or you can call setLayout on a Surface at any time to change the Layout in use:

surface.setLayout({type:"Circular"});

Parameters

Each layout supports its own set of parameters. You set these inside a parameters member in the object that you pass to the layout member
of a render call, or in the object you pass to a setLayout call.

In the first example above, for instance, we can see that the Hierarchical layout supports a padding parameter, which is an array of [ x, y ]
padding values. For a discussion of the supported parameters for each layout, see the sections below.

Refresh vs Relayout

All Layouts offer two basic methods for user interaction (although, as mentioned above, you do not call these methods directly on a Layout, you call them on a Surface): refresh and relayout.

relayout

Calling relayout will cause a Layout to clear all positioning data and start over from scratch. This is what is automatically called at the end of a data load, and can also be called manually should you wish to. For deterministic Layouts such as Hierarchical, you can be sure that relayout will always produce the same result from some given input. For other layouts, such as Spring, this is not the case: the Spring layout initially positions Nodes according to some random scheme, and so each time you run the algorithm you get a unique result.

Another distinction between relayout and refresh is that with relayout you can pass in a new set of parameters

refresh

Calling refresh will cause a Layout to be re-run without first resetting itself. As mentioned above, for most Layouts this will not be any different from a relayout.

For other types of layout - Hierarchical, for instance - there is no discernible difference between a refresh and a re-layout, except for the fact that the relayout method allows you to pass in a new set of parameters for the layout to use.

Whenever a change is made to the data model, the default behaviour is to make an internal call to refresh. This keeps the state of the UI as constant as possible for the user. You can suppress this behaviour by setting refreshAutomatically:false to a render call on an instance of the jsPlumb Toolkit. Should you wish to force a refresh or re-layout yourself, the Surface component offers methods for you to do so:

Note that when you call relayout you can pass a new set of options to the layout.

Another important consideration is that refresh retains existing Nodes, and therefore any event listeners you have registered, whereas relayout empties the DOM and creates everything anew, requiring that you re-register any event listeners. The Toolkit fires events whenever either of these methods are executed - see the documentation on Events.

And at some point you wanted to apply a Spring layout just to clean up the UI. You can call adHocLayout for this:

surface.adHocLayout({type:"Spring"});

The format of the argument to adHocLayout is the same as for any call to setLayout or for a layout specified in a view.
If you have your own layouts registered with the Toolit they can of course be referenced by this method too.

Magnetization

The AbstractLayout class (and therefore every other Layout) has the ability to apply "magnetization" to the
Nodes in the layout in order to push everything apart so that no two nodes overlap. Magnetization can be switched on
permanently (executed after every refresh or relayout), or it can be run only on demand.

This functionality is very useful for helping users maintain a legible UI.

Permanent Magnetization

To switch on permanent magnetization, set magnetize:true in the layout options to a render method call:

Lifecycle

reset is called before every relayout or refresh that occurs on the layout.

decorate is called before every relayout or refresh, immediately after reset.

Lifecycle Method Parameters

Each method is passed a params object, the contents of which are as follows:

reset

remove(el, doNotRefresh)

This is a function you can call to have the Toolkit remove some element from the canvas (or a floated element).
You should use this method in preference to some other way of removing content since it ensures everything is
cleaned up appropriately.

decorate

adapter

This is the underlying Surface that is rendering your content. In future releases of
the Toolkit that support server side rendering, this may be some other object, the interface it implements
will be the same.

append(el, id, pos)

A function you can call to append some element to the canvas. Elements appended in this way are panned and
zoomed with the rest of the content. Only DOM elements are supported as el (not jQuery selectors). id is
optional, and pos is location data in the form {left:..., top:...}

floatElement(el, pos)

A function you can call to append some element to the viewport at a given position. Elements added to the viewport
float over the content and remain fixed at the given location. Note that we do not currently support specifying
right or bottom properties in this method - the values in pos are expected to be [left, top].

fixElement(el, axes, pos)

This behaves like append in that it adds the given element to the canvas so that it is panned and zoomed with
the rest of the content, but the axes argument allows you to specify that the element should not be allowed to
exit the viewport in one or more axes. An example will be best to explain this:

varel=document.createElement("div");el.innerHTML="I'm a label";params.fixElement(el,{left:true},[50,50]);

In this example we have requested that our label div be placed on the content at position [50, 50]. But when the
content is panned to the left to the extent that the label would be less than 50 pixels from the edge of the
viewport, its location is adjusted so that it remains fixed in place in the x axis. In case it is not obvious,
you could also specify top:true in the axes argument.

bounds

This is an array of [ xmin, ymin, xmax, ymax ] locations, giving the extents of the content. Remember that in
the Toolkit, the origin of the canvas does not necessarily correlate with the top left corner of your layout's
extents. It depends on what the layout chooses to do. All of the layouts that ship with the Toolkit, for instance,
routinely draw into the negative in both axes. So the point here is that it may be the case that if you want
something to appear at the origin of your layout's extents, that point will not be [0,0]. It will, though,
be the first two values in bounds, and you are safe to pass bounds to either append or fixElement:

varel=document.createElement("div");el.innerHTML="I'm a label";params.fixElement(el,{left:true},params.bounds);

Here we have requested that our label appear at the origin of the layout's extents, and for it to stay there
in the X axis as the user pans to the left.

jsPlumb

The underlying jsPlumb renderer. The majority of use cases will not need to access this.

layout

The layout used to render this data. Typically, a decorator and a layout work in tandem: the layout knows the
key bits of information a decorator needs. Take a process flow diagram as an example: the layout has created the
lanes and placed nodes into these lanes; it is the layout that can tell the decorator where the lanes start and end.

setAbsolutePosition(el, pos)

A function you can use to set the position of some element on the canvas. el is a DOM element, and pos is an array of
[left,top] pixel values.

toolkit

The underlying jsPlumb Toolkit instance.

Invoking a decorator

You invoke decorators by specifying them in the layout section of a render method call:

You may have noticed in the constructor for the Example decorator above there were two arguments: decoratorParams and
surface. The second of these is the underlying Surface widget. The first argument would be an empty object in the
arrangement shown above, but you can pass constructor parameters into a decorator like this:

Z Index

The Toolkit will not take care of z-index for you. Webapps come in a million different shapes and sizes; it would
be too invasive for the Toolkit to infer anything. Keep this in mind when you write your decorators...

Hierarchy Decorator

From version 1.1.0, the Toolkit ships with a decorator you can use to draw a background behind a Hierarchical layout.
You use it like this:

3. Implement lifecycle methods

The Renderer in the jsPlumb Toolkit will never directly call your layout (unless you accidentally override one of the methods in AbstractLayout, that is!); instead it interfaces with AbstractLayout, which works with your layout via a few lifecycle methods. All of these methods, except step, are optional.

The Layout Lifecycle

AbstractLayout has two methods that a Renderer will call:

layout() runs the layout, without resetting it.

relayout([parameters]) runs the layout, first resetting it.

You should not override these methods. Your layout will not work if you do so.

relayout([parameters])

When relayout() is called, AbstractLayout resets the current parameters, then calls its own private _reset() method, which calls

this.reset()

...if the subclass has defined such a method. It then calls this.layout().

layout()

When layout() is called, AbstractLayout calls

this.begin(toolkit,parameters);

if the subclass has defined it. It then repeatedly calls

this.step(toolkit,parameters);

which subclasses are expected to implement, until the subclass calls

_super.setDone(true);

At this point, AbstractLayout converts the node positions and actually places nodes on screen.

Last, the AbstractLayout calls

this.end(toolkit,parameters);

...if the subclass has defined such a method.

Note that in all these examples, parameters is an object containing the parameters you set on the Layout, merged on top of the default parameter values for the Layout in effect.

Default Parameters

Each layout can expose a member containing default parameter values:

this.defaultParameters={foo:"bar",etc};

When you make a render call and supply some parameters, they are merged on top of a copy of the default parameters before being supplied to the Layout's lifecycle methods:

The default value of the orientation parameter on the Hierarchical layout is horizontal. So in this example the value is overridden by vertical, but the padding values used will be the defaults.

The same principle applies when you call relayout with some new parameters - say we want to reapply the layout above at some point, but switch it back to horizontal with more padding:

renderer.relayout({orientation:"horizontal",padding:[100,100]});

Positioning Nodes

Obviously it is the subclass's responsibility to place all the nodes. The return value from AbstractLayout's constructor has several methods you can use to find out information you need:

getPosition(nodeId) gets a node's current position, creating and randomly assigning one if it has not yet been placed.

setPosition(nodeId, x, y) sets a node's new position.

getSize(nodeId) Returns the size of a node's element in the display.

Viewport Size

The size of the enclosing viewport is exposed on the _super object with two properties:

width the width of the viewport, in pixels.

height the height of the viewport, in pixels.

You may or may not need to care about the size of the viewport. Many Layouts do not, since the renderer provides a pannable surface whose extents may lay outside the bounds of the viewport. But some Layouts may wish to paint themselves entirely inside their viewport.

Example

Taking the code from above, here is a layout that draws all the nodes in the Toolkit instance in a straight line across the middle of the viewport, with 50 pixels spacing (by default; you can pass this value in as a parameter) between them:

;(function(exports){exports.myLayout=function(){var_super=jsPlumbLayout.AbstractLayout.apply(this,arguments),position=0,nodeCount=0,padding;// called by superclass on relayout this.reset=function(){position=0;nodeCount=0;};// called by superclass on relayout OR refresh this.begin=function(graph,toolkit,parameters){nodeCount=toolkit.getNodeCount();padding=parameters.padding||50;};this.step=function(graph,toolkit,parameters){for(vari=0;i<nodeCount;i++){varnode=toolkit.getNodeAt(i),size=toolkit.getNodeSize(node.id);// position around middle of the y axisvary=(_super.height-size[1])/2;// step across in the x directionvarx=counter;// set the position for this node_super.setPosition(node.id,x,y);// increment countercounter+=(size[0]+padding);}// inform the super that the layout is done._super.setDone(true);};this.end=function(graph,toolkit,parameters){// unused.};};})(jsPlumbToolkit.Layouts);

Note in this example how we call _super.setDone(true) right at the end of the first step call. This is because this layout is able to do everything in one pass. Some types of layouts - Spring, for instance - work iteratively until some condition is met.

Magnetization

As discussed above, it is possible for a layout to switch on magnetization by default. If you
wish to do this for your custom layout, you must set defaultMagnetized:true in your constructor before calling
the AbstractLayout constructor: