Skip to main content

Magnetizer

The magnetizer is a extremely useful piece of JsPlumb's UI functionality, providing a means to nudge elements around in your UI so that things don't overlap. There are a few different ways to use the magnetizer - you can instruct your layout to apply the magnetizer after the layout is run, or you can use it to manage element dragging. You can also call it programmatically on an ad-hoc basis.

Layout magnetization

All layouts support the option of switching on magnetization, which is run after the layout runs. For some layouts this ability is theoretically of no use, for instance the Force directed layout, because in that layout the elements in your UI have already been moved apart by the layout itself. But if you're using, for instance, the Absolute layout, then there is a chance one or more elements are overlapping.

You can switch on magnetization in a layout by setting it as a layout option:

Vanilla JS
React
Angular
Vue
Svelte
import { newInstance,
AbsoluteLayout } from "@jsplumbtoolkit/browser-ui"

const toolkit = newInstance()

const surface = toolkit.render(someElement, {
"layout": {
"type": AbsoluteLayout.type,
"options": {
"magnetize": true
}
}
});

Each time the layout runs, the magnetizer will be run afterwards.

Magnetizing after dragging

You can instruct the magnetizer to run after some vertex has been dragged:

Vanilla JS
React
Angular
Vue
Svelte
import { newInstance } from "@jsplumbtoolkit/browser-ui"

const toolkit = newInstance()

const surface = toolkit.render(someElement, {
"magnetize": {
"afterDrag": true
}
});

Try dragging one element on top of another element in the canvas below. At the end of the drag the magnetizer will run. The element you just dragged will be the "focus", and will not move from where you placed it:


Repositioning the dragged element

As mentioned above, the dragged element will remain stationary if the magnetize is run after a drag. You can change that behaviour, though:

Vanilla JS
React
Angular
Vue
Svelte
import { newInstance } from "@jsplumbtoolkit/browser-ui"

const toolkit = newInstance()

const surface = toolkit.render(someElement, {
"magnetize": {
"afterDrag": true,
"repositionDraggedElement": true
}
});

Try dragging one element on top of another element in the canvas below. At the end of the drag the magnetizer will run, but this time the dragged element will be the one that is moved if there is any overlap:

Magnetizing while dragging

You can run the magnetizer while dragging an element by using the constant flag:

Vanilla JS
React
Angular
Vue
Svelte
import { newInstance } from "@jsplumbtoolkit/browser-ui"

const toolkit = newInstance()

const surface = toolkit.render(someElement, {
"magnetize": {
"constant": true
}
});

Trackback

When running the magnetizer in constant mode, elements are pushed out of the way of the element that is being dragged. This is very useful and cool, but once an element has been pushed from its original location it doesn't go back, and so its easy to accidentally scatter your elements all over the canvas. Enter trackback mode: when this is switched on, the magnetizer attempts to revert any moved elements back to their location at the start of an element drag. This is super slick and we're pretty pleased with it.

Vanilla JS
React
Angular
Vue
Svelte
import { newInstance } from "@jsplumbtoolkit/browser-ui"

const toolkit = newInstance()

const surface = toolkit.render(someElement, {
"magnetize": {
"constant": true,
"trackback": true
}
});

Try dragging node 1 through the gap between nodes 2 and 3 in this canvas - they'll move out of the way as node 1 moves through, and then spring back:


Once you release the mouse button, the position of each node is fixed. The canvas above was created with these render options:

{
magnetize:{
constant:true,
trackback:true
}
}

but trackback mode is always available to your users when constant is switched on, even if you do not set trackback:true - they just need to hold down the shift key while dragging. Try it yourself here: when you drag node 1 this time, nodes 2 and 3 will move out of the but they will not spring back, initially. Hold down the shift key, though, and you will see the trackback mechanism kick in:


This canvas was created with only constant switched on:

{
magnetize:{
constant:true
}
}

Finally, note that if you do have trackback:true, your users can hold down the shift key to temporarily switch it off. It can be useful for your users to be able to move some element using the magnetizer which they do not want to revert.

Adhoc magnetization

You can instruct the Surface widget to run the magnetizer over the entire contents of the UI at any time via the magnetize method:

magnetize(focus?:string|Vertex):void

By default, the magnetizer will use the computed center of all the elements as its origin, and all elements will be pushed away from that point, as in this call:

surface.magnetize()

An alternative, though, is to supply a vertex that you wish to act as the origin, and which you do not wish to move:

surface.magnetize("nodeId")

Here we passed the ID of some node, but we could also have passed the node itself. The signature of the magnetize method is:

magnetize(focus?:string|Vertex):void

Setting a vertex position and magnetizing

Another feature the Surface offers is the ability to set the position of some element and to immediately magnetize the UI after setting the element's position, using that element as the focus:

setMagnetizedPosition(element:string|Vertex, x:number, y:number):void

This is effectively the same as:

surface.setPosition(element, x, y)
surface.magnetize(element)

This operation is wrapped in a transaction on the model so if undo is called then every element affected by the magnetize is relocated to its original position.

Gathering Elements

The magnetizer can also gather elements to bring them closer together around some origin. Internally this works by pulling each affected element in on its radial from the origin to the element's center, and then running the magnetizer to nudge everything back out so that nothing overlaps.

gather(focus?:string|Vertex):void

To gather all the elements in the UI around their computed center, don't supply a focus element:

surface.gather()

To gather all the elements in the UI around some focus element:

surface.gather(idOrVertex)

This code sets up a surface so that when you click on one of the nodes, the other nodes in the surface are gathered around it:

Vanilla JS
React
Angular
Vue
Svelte
import { newInstance } from "@jsplumbtoolkit/browser-ui"

const toolkit = newInstance()

const surface = toolkit.render(someElement, {
"zoomToFit": true,
"view": {
"nodes": {
"default": {
"events": {
"tap": (p) => {
p.renderer.gather(p.obj)
}
}
}
}
}
});

Try clicking one of the nodes in the canvas below. You'll see the other two nodes are gathered in to it. Note that nodes are gathered along a path from the current location to the focus node, and so other nodes may still obstruct their travel. A good example of that is if you click node 1: node 2 is pulled in close to node 1, but node 3 can only travel until it is blocked by node 2.


All options

The Surface offers many options for configuring the magnetizer. The full list is:

Interface SurfaceMagnetizeOptions

NameTypeDescription
afterDrag?booleanIf true, magnetizer will be run after a vertex is dragged.
afterGroupCollapse?booleanDefaults to false. Indicates the surface should gather nodes around a newly collapsed group
afterGroupExpand?booleanDefaults to false. Indicates the surface should magnetize nodes around a newly expanded group
afterGroupGrow?booleanDefaults to false. Indicates the surface should magnetize/gather nodes around a newly resized group if the group size was enlarged.
afterGroupResize?booleanDefaults to false. Indicates the surface should magnetize/gather nodes around a newly resized group, regardless of whether the group size grew or if it shrunk. This flag is the same as setting afterGroupShrink and afterGroupGrow
afterGroupShrink?booleanDefaults to false. Indicates the surface should magnetize/gather nodes around a newly resized group if the group size was reduced.
afterLayout?booleanIf true, magnetizer will be run after the layout is run.
constant?booleanIf true, magnetizer will be run constantly as a vertex is being dragged, pushing other vertices out of the way of the vertex that is being dragged
constrainToViewport?booleanIf true, vertices moved by the magnetizer will be constrained to move within the visible viewport, which is a function of the current zoom/pan of the surface. Otherwise, vertices will be able to be pushed out of the visible viewport.
repositionDraggedElement?booleanIf true, and afterDrag is true, when the magnetizer is run after a drag it will be the recently dragged element that moves in precedence to the other elements. By default, the recently dragged element is _not_ moved by the magnetize operation - it stays where you dragged it.
trackback?booleanUsed in conjunction with constant: attempts to reposition any moved elements to their original positions, or as close as possible, each time the magnetizer runs a sweep. Defaults to false.
trackbackThreshold?numberWhen in constant mode and trackback is set, this value specifies a threshold for elements that have been pushed from their original position. Beyond this threshold, the magnetizer will no longer attempt to track an element back. This can be useful in certain situations where the user does want to move an element by pushing it via the magnetizer

These options should be supplied as the magnetize options for a Surface's renderOptions:

Vanilla JS
React
Angular
Vue
Svelte
import { newInstance } from "@jsplumbtoolkit/browser-ui"

const toolkit = newInstance()

const surface = toolkit.render(someElement, {
"magnetize": {
"afterDrag": true,
"afterGroupGrow": true
}
});