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:
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:
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:
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:
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.
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:
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
Name | Type | Description |
---|---|---|
afterDrag? | boolean | If true, magnetizer will be run after a vertex is dragged. |
afterGroupCollapse? | boolean | Defaults to false. Indicates the surface should gather nodes around a newly collapsed group |
afterGroupExpand? | boolean | Defaults to false. Indicates the surface should magnetize nodes around a newly expanded group |
afterGroupGrow? | boolean | Defaults to false. Indicates the surface should magnetize/gather nodes around a newly resized group if the group size was enlarged. |
afterGroupResize? | boolean | Defaults 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? | boolean | Defaults to false. Indicates the surface should magnetize/gather nodes around a newly resized group if the group size was reduced. |
afterLayout? | boolean | If true, magnetizer will be run after the layout is run. |
constant? | boolean | If 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? | boolean | If 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? | boolean | If 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? | boolean | Used 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? | number | When 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
:
import { newInstance } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance()
const surface = toolkit.render(someElement, {
"magnetize": {
"afterDrag": true,
"afterGroupGrow": true
}
});