- Panning
- Disabling Panning
- Filtering Panning
- Pan Nudge Bars
- Hiding the Pan Buttons
- Zooming
- zoomToVisible(options)
- zoomToVisibleIfNecessary(options)
- Panning
- Zooming
- Clamping
- Positioning
- Dragging Nodes
- Selecting Nodes
- Events
- Background Images
- Saving/Restoring State
- Appearance
- CSS Classes
- Miscellaneous
Surface Widget
The Surface widget is an 'infinite pan' canvas that does not use scrollbars. To pan the canvas, the user drags using the left mouse button, or, on touch devices, by dragging a single touch.
Panning is normally enabled, but a Surface can be initialized with panning disabled by setting enablePan
to false:
You can also disable a Surface entirely via the setMode
method:
It's a fairly common use case that there be some set of elements in your canvas on which a drag should not cause a pan to occur. To handle this, the Surface has the panFilter
parameter. This is either a String that is a CSS selector representing elements that should allow a pan to begin, or a Function from which you should return true if you would like a pan to begin.
CSS Selector Filter
Function filter
let surface = toolkit.render({
container:"someElement",
panFilter:function(eventTarget) {
return someLogic(eventTarget);
}
});
The Surface draws a 'nudge' bar on each edge, which the user can click on to pan the canvas by a small distance in the given direction, or click and hold for continuous panning in that direction.
The default distance moved by a click (or a tick, when held continuously) is 50 pixels. This can be set on the render
call:
You can do this via CSS, since each pan button has the class jtk-surface-pan
. You can also do this via a parameter on the render
call:
The default behaviour of the Surface is to support zooming via the mouse wheel, or on touch devices, via pinch to zoom.
Constructor parameters
These parameters can be passed in as part of the render
call to a Toolkit instance:
- enableWheelZoom Defaults to true. Whether or not zooming with the mouse wheel is enabled.
- wheelFilter Optional CSS selector representing elements that should not respond to wheel zoom. Defaults to empty.
- wheelZoomMetaKey Defaults to false. If true, the wheel zoom only fires when Ctrl (or CMD on Mac) is pressed and the wheel is rotated.
- wheelReverse Defaults to false. If true, the zoom direction is reversed: wheel up zooms out, and wheel down zooms in.
Zoom Methods
There are a few helper methods for zooming exposed on the Surface.
zoomToFit(options)
Zooms the display so that all tracked elements fit inside the viewport (including invisible nodes). This method will also increase the zoom if necessary in order that the content fills the 90% of the shortest axis of the viewport.
The most basic call you can make is this:
zoomToFit
supports a number of parameters:
- fill A decimal indicating how much of the viewport to fill with the zoomed content. Defaults to 0.9.
- padding Padding to leave around all elements. Default is 20 pixels.
- onComplete Optional function to call on operation complete (centering may be animated).
- onStep Optional function to call on operation step (centering may be animated).
- doNotAnimate By default, the centering content step does not use animation (this parameter is set to true). This is due to this method being used most often to initially setup a UI.
- doNotZoomIfVisible Defaults to false. If true, no action is taken if the content is currently all visible.
- doNotFirePanEvent Defaults to false. If true, a pan event will not be fired.
An example showing the Surface animating the content until it fits 80% of the viewport and then popping up an alert:
To zoom to show only visible nodes, see zoomToVisible.
zoomToFitIfNecessary(options)
Works like zoomToFit
but if all tracked elements are currently visible does not adjust the zoom.
All of the parameters supported by zoomToFit
are supported by zoomToFitIfNecessary
.
zoomToBackground(options)
If a background was set, zooms the widget such that the entire background is visible.
This method also supports onStep
and doNotAnimate
.
zoomToSelection(options)
Zoom to either the current selected set of nodes in the Toolkit (defaults to the current selection filling 90% of the shortest axis in the viewport):
or provide a Selection of your own to which to zoom:
To find our more about Selections, see here.
This method also supports an optional filter
function, which is used to create a Selection by running it through the Toolkit's filter
method. For instance, this is how you could create what the zoomToVisible
method (described below) does:
surface.zoomToSelection({
filter:function(obj) { return obj.objectType === "Node" && surface.isVisible(obj); }
});
Zooms to display all the currently visible nodes. All animation options are supported by this method - it is a wrapper around zoomToSelection
in which we first create a selection representing all the visible nodes.
This method is to zoomToVisible
as zoomToFitIfNecessary
is to zoomToFit
- the content will be centered, but the zoom level will be changed only if all of the currently visible nodes are not visible in the viewport.
setZoom(value)
Sets the current zoom level. This must be a positive decimal number. If it is outside of the current zoom range, it will be clamped to the zoom range.
Here we have set the zoom to 50%.
nudgeZoom(amount)
Nudges the current zoom level by some value (negative or positive).
// nudge the zoom by 5%
surface.nudgeZoom(0.05);
// nudge the zoom by -15%
surface.nudgeZoom(-0.15);
setZoomRange(range, doNotClamp)
Sets the current zoom range. By default, this method checks if the current zoom is within the new range, and if it is not then setZoom
is called, which will cause the zoom to be clamped to an allowed value in the new range. You can disable this by passing true
for doNotClamp
.
here we have set the zoom range to be 50% minimum, 200% maximum. If the current zoom was outside of this range, it was clamped to be within.
Let's set the zoom to 2, the top of our current range, and then adjust the zoom range without affecting the widget's zoom:
Clamping
Pan Movement
By default, the Surface will clamp movement when panning so that some content is always visible. This can be overridden:
Zoom Movement
It is also default behaviour to clamp the movement of the canvas when the user zooms such that some content is always visible. Without this, it's quite easy for a user to accidentally zoom in such a way that all of the content disappears (consider the case that the canvas is zoomed out a long way and the user then zooms in on some whitespace that is a long way from any content).
As with pan clamping, you can also switch off zoom clamping if you wish:
Background
This is discussed below, but a brief mention should be made of the fact that you can also tell the Surface to clamp movement such that part of the background is always visible:
Positioning
Several methods are available to assist you with positioning the Surface canvas. These are discussed in greater detail in the API documentation:
- centerOn(node)
Takes a node as argument and positions the Surface canvas such that the given node is at the center in both axes.
- centerOnHorizontally(node)
Takes a node as argument and positions the Surface canvas such that the given node is at the center in the X axis.
- centerOnVertically(node)
Takes a node as argument and positions the Surface canvas such that the given node is at the center in the Y axis.
- centerContent()
Centers the content in the viewport (without altering the zoom, so the content may still extend past the viewport bounds).
- pan(x, y)
Pans the canvas by a given amount in X and Y.
- positionElementAt(el, x, y)
Positions a DOM element at a given X,Y on the canvas, in canvas coordinates (meaning it takes into account the current zoom and pan). This is not intended for use with elements the Surface is managing: it is designed to be used with elements such as pop-ups that you may wish to position relative to the content in your canvas.
- positionElementAtEventLocation(el, event)
Positions a DOM element at the apparent canvas location corresponding to the page location given by some event. This is not intended for use with elements the Surface is managing: it is designed to be used with elements such as pop-ups that you may wish to position relative to the content in your canvas.
- positionElementAtPageLocation(el, x, y)
Positions a DOM element at the apparent canvas location corresponding to the given page location. This is not intended for use with elements the Surface is managing: it is designed to be used with elements such as pop-ups that you may wish to position relative to the content in your canvas.
Dragging
By default, any nodes rendered by a Surface will be initialized to be draggable. This can be overridden:
Drag Options
You can provide a set of options that will be applied to every node when it is made draggable:
Valid values for the contents of dragOptions
are any valid parameters in Katavorio, the drag library used by jsPlumb.
Selecting nodes
Nodes managed by a Surface widget may be "selected" at any point in time programmatically via the associated Toolkit instance. When a node is selected, the Surface is informed, and its DOM element is assigned the class jtk-surface-selected-element
.
You can also select nodes using the mouse, with a "lasso". To switch into this mode, call setMode
:
Lasso Behaviour
The lasso works in one of two ways: when the mouse is travelling from left to right, any node that intersects the lasso will be selected. When travelling from right to left, however, only nodes that are fully enclosed by the lasso will be selected.
Lasso Start Filter
You may need to specify one or more elements on which a mousedown should not cause the selection lasso to activate. You can do that with the lassoFilter
parameter:
Here we tell the Surface that a mousedown on any element having the class delete
or add
should not cause the lasso to activate - perhaps they are classes assigned to buttons that fire actions in our UI. The format of lassoFilter
is any valid CSS selector string.
Lasso Select Filter
There is also a finer-grained means of controlling which nodes should be selected by the lasso - the lassoSelectionFilter
function. This can be specified in the render call:
var surface = toolkit.render({
...
lassoSelectionFilter:function(node) {
return node.data.foo != null;
}
});
The function is given node objects, and is considered to accept the node if anything other than a boolean false
value is returned. In this example, then, we reject nodes whose foo
value is null
.
You can update this function at runtime, in two ways. First, directly via the setLassoSelectionFilter
method:
...you can also provide a selection filter as an option to the third argument of a setMode
call:
surface.setMode("select", null, {
lassoSelectionFilter:function(node) {
return node.data.foo < Math.PI;
}
});
Exiting Select Mode
Ordinarily, the Surface will jump back into Pan mode from Select mode after some nodes have been selected, but this behaviour can be overridden, using the autoExitSelectMode
flag:
Events
Event listeners can be bound to the Surface widget in one of three ways:
- via the
bind
method - in the
events
parameter to arender
call on an instance of the Toolkit - in the individual node, Port and Edge definitions in the
view
parameter to arender
call.
The full list of bindable events is below.
Bind
var surface = toolkitInstance.render({
container:"someElement",
...
});
surface.bind("canvasClick", function(e) {
console.log("click on the canvas");
});
surface.bind("nodeAdded", function(params) {
});
Suspending Events
You can suspend/enable events from the Surface widget with this method:
You can also wrap the underlying Toolkit's batch
command (which runs a series of operations without making any rendering changes) with the Surface widget's batch
command:
surface.batch(function() {
toolkit.addNode({id:"foo"});
toolkit.addNode({id:"bar"});
toolkit.addNode({source:"foo", target:"bar"});
});
This is equivalent to:
surface.setSuspendEvents(true);
toolkit.batch(function() {
toolkit.addNode({id:"foo"});
toolkit.addNode({id:"bar"});
toolkit.addNode({source:"foo", target:"bar"});
});
surface.setSuspendEvents(false);
Events Parameter
You can provide a set of event listeners via the events
parameter on a render
call:
var surface = toolkitInstance.render({
container:"someElement",
...
events:{
"canvasClick":function(e) {
console.log("click on the canvas");
},
"nodeAdded":function(params) {
}
}
});
Each of these entries is equivalent to first instantiating the Surface and then calling bind
on it:
var surface = toolkitInstance.render({
container:"someElement",
...
});
surface.bind("canvasClick", function(e) {
console.log("click on the canvas");
});
surface.bind("nodeAdded", function(params) {
});
View Events
Each entry in the nodes
, groups
, ports
or edges
section of the view
you pass to a render
call can also specify a list of events. The advantage to doing this is that you can be very granular in your event registrations, limiting listeners to objects of some specific type as you need to. The disadvantage is of course more complexity in configuring everything.
Here's a simple example showing two node types, one of which declares a dblclick
listener:
var surface = toolkitInstance.render({
container:"someElement",
view:{
nodes:{
"type_a":{
template:"TypeA"
},
"type_b":{
template:"TypeB",
events:{
dblclick:function(node) {
console.log("double click on node " + node.id);
}
}
}
}
}
});
View Event Inheritance
If you have a type declared that extends some other type, it will inherit the parent type's listeners. But if your subtype declares some event listener, that listener will override the parent's listener for the given event, if one is present. For example:
var surface = toolkitInstance.render({
container:"someElement",
view:{
nodes:{
"type_a":{
template:"TypeA",
events:{
dblclick:function(node) {
console.log("type A double click");
}
}
},
"type_b":{
parent:"type_a",
events:{
dblclick:function(node) {
console.log("type B double click");
}
}
}
}
}
});
A click on a node of type type_b
will produce a single line in the console: "type B double click"
.
Subscribable Events in Views
This is the full list of events to which you can subscribe in a View definition:
- click - Notification that a click event occurred
- tap - A synthesized click event that normalises click behaviour between touch and mouse devices
- dblclick - Notification that a double click event occurred
- mouseover - Notification the mouse entered the object
- mouseout - Notification the mouse left the object
- mousedown - Notification a mouse button was pressed on the object
- mouseup - Notification a mouse button was released
- contextmenu - Notification that a context menu event occurred on a node. This typically means a right click.
Each of these callback functions is given a JS object, the contents of which differs between Edges and node/Ports.
Node Callback Parameters
Port Callback Parameters
Edge Callback Parameters
{
connection:Connection,
edge:Edge,
e:Event,
toolkit:jsPlumbToolkitInstance,
renderer:AbstractRenderer
}
List of Events
Event | Description | Parameters |
---|---|---|
nodeAdded | This is a notification that a new node has been added to the UI. |
|
nodeRendered | This is a notification that a node has been rendered to the DOM: it is only fired if you create a Surface for an instance of the Toolkit that already contains some data. You can use this event in the same way as you would use `nodeAdded`: to attach behaviour to the node. |
|
nodeRemoved | This is a notification that a node has been removed. |
|
nodeMoveStart | Notification that a given node has just begun to be dragged. |
|
nodeMoveEnd | Notification that a given node has ceased movement. |
|
edgeAdded | Notification that a new Edge was established, either programmatically or by the user. Note that this is different from the `nodeRemoved` event described below, which is fired __only__ when the Edge is removed through user intervention in the UI. |
|
edgeRemoved | Notification that an Edge was removed. This event is fired only when the Edge was removed through user interaction with the UI, not when an Edge was removed programmatically. |
|
portAdded | Notification that a new Port has been added to some node. |
|
portRemoved | Notification that a Port has been removed from some node. |
|
anchorChanged | Notification that the anchor position related to some Port has changed. This event is not something that the majority of applications will need to care about. |
|
modeChanged | Notification that the Surface's mode has changed. |
|
objectRepainted | Notification that some Toolkit object was repainted. |
|
pan | Notification that the Surface has been panned |
|
zoom | Notification that the Surface has been zoomed |
|
canvasClick | Notification that the canvas background was clicked. |
|
canvasDblClick | Notification that the canvas background was double-clicked. |
|
relayout | Notification that the layout associated with some Surface performed a full relayout. | |
click | Click event on a node or Port. |
|
click | Tap event on a node or Port. |
|
dblclick | Double click event on a node or Port. |
|
contextmenu | Context menu event on a node or Port. |
|
mousedown | Mouse down event on a node or Port. |
|
mouseup | Mouse up event on a node or Port. |
|
mouseover | Mouse enter event on a node or Port. |
|
mouseout | Mouse out event on a node or Port. |
|
mouseover | Mouse over event on a node or Port. |
|
mousemove | Mouse move event on a node or Port. |
|
Background Images
Single Background Image
To display a single image as the background of the Surface widget:
var toolkit = jsPlumbToolkit.newInstance();
var surface = toolkit.render({
container:"someElement",
background:{
url:"./myBackground.png"
}
});
Tiled Background Image
You can use a tiled image as your background - you need to provide a url pattern, the number of available zoom levels, the tile size, and the width and height of the full image:
To display a single image as the background of the Surface widget:
var toolkit = jsPlumbToolkit.newInstance();
var surface = toolkit.render({
container:"someElement",
background:{
type:"tiled",
url:"tiles/{z}/tile_{x}_{y}.png",
tileSize:[750,750],
width:10000,
height:6000,
maxZoom:3
}
});
Tiled Background Pattern URL
The URL you supply for a tiled background should have a placeholder for each of zoom
, x
and y
, as in the example above. Tiles are zero-indexed, so, for example, the top left tile at zoom level 0 would, in the previous example, generate this url:
The Surface does not support the concept of a "continuous world", in which tiles with negative indices may be requested.
Clamping to the background image
Depending on your use case, you may wish to force the Surface to clamp the pan/zoom such that some portion of the background image is always visible. You do this by setting the clampToBackground
parameter on a render
call:
var toolkit = jsPlumbToolkit.newInstance();
var surface = toolkit.render({
container:"someElement",
clampToBackground:true,
background:{
url:"myBackground.png"
}
});
Zooming to the background image
If you wish to zoom out to the point that the entire background image is visible:
var toolkit = jsPlumbToolkit.newInstance();
var surface = toolkit.render({ ... });
surface.zoomToBackground();
Saving/Restoring State
The Surface widget supports writing/reading its current state to/from local storage. For some applications this is useful, but keep in mind that this is local to the current browser.
State handle
You can provide a state handle to the render
call that instantiates a Surface widget:
Saving State
To save the current state, call saveState
:
You can optionally provide a handle to override the one that you provided to the constructor:
Restoring State
At the time of writing, restoreState
restores only the positions of the elements in the Surface.
As with saveState
, you can optionally provide a handle to override the default:
Clearing State
If you wish to clear out the state from local storage for some Surface, you can:
again, with an optional handle
:
Appearance
It is recommended that you use the view
to configure the appearance of your UI, but should you wish to provide some defaults to the underlying jsPlumb instance, you can do so via the jsPlumb
parameter:
var surface = toolkit.render({
...
jsPlumb:{
Anchor:"Center",
Connector:[ "StateMachine", { curviness:50 } ]
}
});
Valid values for this object are anything that is a valid jsPlumb default
This is a full list of CSS classes used by the Surface widget.
CSS
Class | Description |
---|---|
jtk-surface | Assigned to an element that is acting as a Surface widget. |
jtk-surface-canvas | Assigned to the work area of a Surface widget. This canvas element will have been created automatically by the Surface and is not normally something you will need to style. If you do attach some styles, you should be careful to ensure that this element always has `position:relative;` set. |
jtk-surface-lasso | Assigned to the "lasso" element used when selecting elements in a Surface using the mouse. |
jtk-surface-pan | Assigned to all of the pan buttons in a Surface, regardless of which direction they control. |
jtk-surface-pan-top | Assigned to the pan button that appears on the top edge of the Surface. |
jtk-surface-pan-left | Assigned to the pan button that appears on the topleft edge of the Surface. |
jtk-surface-pan-right | Assigned to the pan button that appears on the right edge of the Surface. |
jtk-surface-pan-bottom | Assigned to the pan button that appears on the bottom edge of the Surface. |
jtk-surface-selected-element | Assigned to any element that is part of some Surface's current selection. |
jtk-lasso | Assigned to the selection lasso element |
Miscellaneous
Consuming Right Click
The default behaviour is to consume any right-click events. This is good when your app is in production, and really annoying when you're in the middle of development. To suppress this behaviour, set consumeRightClick
: