Zoom & Scroll
The plugin to implement scrolling, panning, zooming, centering and auto-resizing of Paper content is called PaperScroller. It wraps the Paper element.
Installation
Import the PaperScroller
from the ui
namespace and create a new instance of it. Pass an instance of dia.Paper
to the constructor.
import { dia, shapes, ui } from '@joint/plus';
const graph = new dia.Graph({}, { cellNamespace: shapes });
const paper = new dia.Paper({
width: 2000,
height: 2000,
model: graph,
cellViewNamespace: shapes
});
const paperScroller = new ui.PaperScroller({
paper: paper
});
document.getElementById('paper').appendChild(paperScroller.render().el);
There is also a UMD version available
Include joint.ui.paperScroller.js
and joint.ui.paperScroller.css
to your HTML:
<link href="joint.ui.paperScroller.css" rel="stylesheet" type="text/css">
<script src="joint.js"></script>
<script src="joint.ui.paperScroller.js"></script>
And access the PaperScroller
through the joint.ui
namespace:
const graph = new joint.dia.Graph({}, { cellNamespace: joint.shapes });
const paper = new joint.dia.Paper({
width: 2000,
height: 2000,
model: graph,
cellViewNamespace: joint.shapes
});
const paperScroller = new joint.ui.PaperScroller({
paper: paper
});
document.getElementById('paper').appendChild(paperScroller.render().el);
How does PaperScroller work?
The ui.PaperScroller
constructor takes a Paper object and creates a window into the Paper area. The next thing to do is rendering the PaperScroller (paperScroller.render()
) and appending the PaperScroller HTML element (el
) to a holder, which can be any element in your HTML. This way, the actual Paper can be large but the user will see only the PaperScroller area which they can scroll and pan.
Panning
You can set up the panning interaction easily by hooking up the paperScroller.startPanning()
method to the blank:pointerdown
Paper event, which will initiate panning whenever the user drags a blank area of the Paper:
paper.on('blank:pointerdown', paperScroller.startPanning);
Set mouse cursor
In order to help users understand what action will happen when they drag the PaperScroller, use the paperScroller.setCursor()
method - it sets the cursor applied when the user's mouse pointer is over the PaperScroller:
paperScroller.setCursor('crosshair');
Alternatively, you may pass the cursor
option to the PaperScroller constructor:
const paperScroller = new joint.ui.PaperScroller({
paper: paper,
cursor: 'grab'
});
Padding
You can set default padding around the Paper's content by passing a padding
option to the PaperScroller constructor. The option may be specified as a number, an object, or a function. For example:
const paperScroller = new ui.PaperScroller({
paper: paper,
padding: { top: 20, left: 20 }
});
If you do not provide your own paperScroller.options.padding
, a default function is used, which ensures that the Paper inside the PaperScroller is always pannable all the way to the left, right, top and bottom, while also making sure that there is always at least a fragment of the Paper visible. See the API reference for more details.
Borderless padding
The optional borderless
parameter changes the default behavior of PaperScroller padding. If enabled, the Paper inside the PaperScroller is visually extended into the padding area (which would normally be blank), which has the effect of making the Paper's borders visible no more.
Inertia
The PaperScroller constructor also takes an optional inertia
parameter. If enabled, panning of the PaperScroller will have an inertia effect where the PaperScroller builds momentum while dragging and continues moving in the same direction for some time after the user stops dragging.
Scrolling
User scrolling of the Paper is allowed by default. Two methods are provided to toggle this functionality - paperScroller.lock()
and paperScroller.unlock()
.
PaperScroller also provides methods for programmatic scrolling. These methods try scroll the PaperScroller so that a specified point on the Paper lies at the center of the PaperScroller viewport. If this is not possible (e.g. if the requested point lies in a corner of the Paper), the PaperScroller is scrolled as far as possible without requiring additional padding. This means that the requested point will not actually move into the center of the viewport if there is not enough room for it.
Use the center
functions to add paddings if necessary and force the requested point to move to the center of PaperScroller viewport.
paperScroller.scroll(x, y)
tries to scroll the PaperScroller so that the point (x
,y
) on the Paper (in local coordinates) is at the center of the PaperScroller viewport.paperScroller.scroll(x)
tries to scroll the PaperScroller so that the point (x
,[y of center of currently visible area]
) on the Paper (in local coordinates) is at the center of the PaperScroller viewport.paperScroller.scroll(null, y)
tries to scroll the PaperScroller so that the point ([x of center of currently visible area]
,y
) on the Paper (in local coordinates) is at the center of the PaperScroller viewport.paperScroller.scrollToContent()
tries to scroll the PaperScroller so that the center of Paper content is at the center of the PaperScroller viewport.paperScroller.scrollToElement(element)
tries to scroll the PaperScroller so that the center of the provided Element is at the center of the PaperScroller viewport.
Animation
Animated transitions are better supported with the transition
functions.
All scroll
functions optionally support animation if an opt.animation
object is provided. See the API reference of the paperScroller.scroll()
function for more details.
For example:
paperScroller.scrollToElement(element, { animation: { duration: 200, timingFunction: (t) => t }});
Scrolling while dragging
The PaperScroller constructor takes an optional scrollWhileDragging
parameter. When this parameter is set to true
, the PaperScroller automatically scrolls the PaperScroller to ensure that any dragged Element stays within the viewport.
Centering and positioning Paper content
The center
methods are more aggressive than scroll
methods. These methods position the Paper so that a specific point on the Paper lies at the center of the PaperScroller viewport, adding paddings around the Paper if necessary (e.g. if the requested point lies in a corner of the Paper). This means that the requested point will always move into the center of the viewport.
Use the scroll
functions to avoid adding paddings and only scroll the PaperScroller viewport as far as the Paper boundary.
paperScroller.center()
positions the center of the Paper to the center of the PaperScroller viewport.paperScroller.center(x, y)
positions the point (x
,y
) on the Paper (in local coordinates) to the center of the PaperScroller viewport.paperScroller.center(x)
positions the point (x
,[y of center of currently visible area]
) on the Paper (in local coordinates) to the center of the PaperScroller viewport.paperScroller.center(null, y)
positions the point ([x of center of currently visible area]
,y
) on the Paper (in local coordinates) to the center of the PaperScroller viewport.paperScroller.centerContent()
positions the center of Paper content to the center of the PaperScroller viewport.paperScroller.centerElement(element)
positions the center of the provided Element to the center of the PaperScroller viewport.
The position
methods are a more general version of the center
methods. These methods position the Paper so that a specific point on the Paper lies at requested coordinates inside the PaperScroller viewport.
paperScroller.positionContent(positionName)
aligns Paper content inside the PaperScroller viewport as specified bypositionName
.paperScroller.positionElement(element, positionName)
aligns provided Element inside the PaperScroller viewport as specified bypositionName
.paperScroller.positionRect(rect, positionName)
aligns a custom rectangle inside the PaperScroller viewport as specified bypositionName
.paperScroller.positionPoint(point, x, y)
positions a custompoint
on the Paper (in local coordinates) to the point (x
,y
) inside the PaperScroller viewport (in client coordinates). This is useful when you need complete control over what to position where.
Padding
All center
and position
functions optionally support additional padding of the positioned elements away from the edges of PaperScroller viewport if an opt.padding
object is provided. See the API reference of the paperScroller.center()
function for more details.
The padding stacks up with any x
and y
coordinates; it also affects the center calculations. For example, adding 100px of left padding causes the effective center of the PaperScroller viewport to shift by 50px to the right (in client coordinates), which is where the center of the Paper would end up in the following case:
paperScroller.center({ padding: { left: 100 }});
The same applies when we center a specific point with the same padding option - the effective center of the PaperScroller viewport would shift by 50px to the right (in client coordinates), which is where the point (100,200) of the Paper (in local coordinates) would end up in the following case:
paperScroller.center(100, 200, { padding: { left: 100 }});
Finally, the following example positions point
(in Paper local coordinates) to the point 30px away from right edge of the PaperScroller viewport (10px + 20px padding) and 20px below top edge (10px + 10px padding):
paperScroller.positionPoint(point, 10, 10, { padding: { horizontal: 20, vertical: 10 }});
Paper visibility
PaperScroller provides methods for checking whether Elements or points are visible within the PaperScroller and not scrolled outside the viewport:
paperScroller.getVisibleArea()
answers the questionWhat part of the Paper can be seen by the user through the PaperScroller viewport?
paperScroller.isElementVisible(element)
returnstrue
if the specified Element is visible in the current PaperScroller viewport.paperScroller.isPointVisible(point)
returnstrue
if the specifiedpoint
is visible in the current PaperScroller viewport.
Zooming the Paper
PaperScroller exposes an API to scale the contained Paper more easily:
paperScroller.zoom()
returns the current zoom level of the Paper.paperScroller.zoom(value)
accepts a value by which the scaling factor of the Paper should be increased/decreased. A positive value increases the scaling factor, which causes the Paper to be zoomed in. A negative value decreases the scaling factor, which causes the Paper to be zoomed out.paperScroller.zoomToFit()
scales the Paper so that all of the contents of its Graph fit into the PaperScroller viewport.paperScroller.zoomToRect(rect)
zooms and repositions the Paper so that the area defined byrect
(in Paper local coordinates) fits into the PaperScroller viewport.
Animated transitions
PaperScroller provides a few methods for panning and zooming in animated fashion. These are:
paperScroller.transitionToPoint(x, y)
pans and optionally zooms the PaperScroller to a given point (x
,y
) on the Paper (in local coordinates).paperScroller.transitionToPoint(point)
pans and optionally zooms the PaperScroller to a givenpoint
on the Paper (in local coordinates).paperScroller.transitionToRect(rect)
pans and zooms the PaperScroller over a specific period so that a given rectangular area (in Paper local coordinates) ends up entirely visible in the PaperScroller viewport at the end of the transition.paperScroller.removeTransition()
removes any ongoing PaperScroller transition and prevent the associatedopt.onTransitionEnd
callback from being fired.
Paper auto-resize/shrink
The PaperScroller constructor takes an optional autoResizePaper
parameter. When this parameter is set to true
, the PaperScroller automatically resizes the Paper so that it fits the content inside it. This makes it possible to have a fixed (even smaller) size of the Paper and when the user drags an Element outside this area, the PaperScroller extends this Paper in order to accommodate the Element in its new position.
const paperScroller = new ui.PaperScroller({ autoResizePaper: true });
By default, the PaperScroller resizes the Paper by the Paper's width
/height
. If you want the Paper to extend by a different amount, pass the
baseWidth
/baseHeight
options to the PaperScroller constructor. You can further
adjust the behavior by passing a contentOptions
object to the
PaperScroller constructor. This is handy if you, for example, want to set the maximum width and height of the Paper:
const paperScroller = new ui.PaperScroller({
autoResizePaper: true,
baseWidth: 100,
baseHeight: 100,
contentOptions: {
maxWidth: 3000,
maxHeight: 3000
}
});
Try how this works in the following demo by zooming the Paper out so that you see its borders and then move an Element outside the Paper area. You should see how the Paper gets automatically resized. When you drag the Element back to its original location, the PaperScroller resizes the Paper back to its original size.
Events
The PaperScroller object triggers events that you can react on in your applications. These events can be handled by using the paperScroller.on(eventName, handler)
method. The list of events can be found in the API reference.
Frequently asked questions
How to make the graph use all available space within a container?
While the PaperScroller component automatically adjusts to the dimensions of its host HTML element, this is not true about its content by default. To make the content of the PaperScroller fit within the PaperScroller dimensions, call the paperScroller.zoomToFit()
function dynamically (e.g. in reaction to a change:position
event on the Graph).
If you do not expect the host HTML element to change dimensions and do not need Zoom & Scroll functionality, you may get this behavior even without PaperScroller.
To make the content of the Paper fit within the Paper dimensions area, call the paper.transformToFitContent()
function dynamically (e.g. in reaction to a change:position
event on the Graph).
How to automatically adjust the graph to the center of the paper?
You can position the graph to the center of the PaperScroller using the paperScroller.zoomToFit()
function with verticalAlign
and horizontalAlign
options.
Learn more...
paperScroller.zoomToFit({
padding: 30,
contentArea: graph.getBBox(),
verticalAlign: 'middle',
horizontalAlign: 'middle'
});
If you do not need Zoom & Scroll functionality, you may get this behavior even without PaperScroller.
You can position the graph to the center of the Paper using the paper.transformToFitContent()
function with verticalAlign
and horizontalAlign
options.
paper.transformToFitContent({
padding: 30,
contentArea: graph.getBBox(),
verticalAlign: "middle",
horizontalAlign: "middle"
});
What can be done in case my graph grows bigger than the initially configured paper size?
In general, there are two possible solutions if your graph (i.e. the content of your diagram) grows bigger than the initial paper size - scale the content to fit the paper, or resize the paper to contain all content.
Learn more...
-
To scale the content to fit the paper within a PaperScroller component, you may use the
paperScroller.zoomToFit()
function dynamically - see above. -
To resize the paper within a PaperScroller component based on the content, you may activate the
autoResizePaper
PaperScroller option
If you do not need Zoom & Scroll functionality, you may get this behavior even without PaperScroller.
-
To scale the content to fit the paper, you may use the
paper.transformToFitContent()
function dynamically - see above. -
To resize the paper based on the content, you may use the
paper.fitToContent()
function dynamically (e.g. in reaction to achange:position
event on the Graph.
These Paper options are illustrated in our Paper options demo (Scale content to fit
and Fit to content
, respectively).
How to implement adaptive rendering of sub elements which is dependent on the paper zoom level (ZUI)?
An example of adaptive rendering
/ ZUI (Zooming User Interface) is shown in our ELK demo.
Learn more...
In the demo, when the diagram is zoomed out sufficiently, a minimal view is applied to four elements (Interface - fast
, channel1
, Interface - slow
, channel2
) which hides their contents and applies their label in that space instead.
The demo has its limitations, however:
- Only the elements and links inside the changing containers can be hidden when the paper is zoomed out.
- Meanwhile, the label text inside the changing containers is always rendered but hidden in CSS when the paper is zoomed in.
If you wanted to implement the ZUI functionality on a more granular basis, you would have to write a custom element view.
How can I add support for panning and zooming in my JointJS diagram?
You can add support for panning and zooming by replacing the JointJS Paper in your code with JointJS+ PaperScroller, and adding a listener for paper events related to user interaction that indicates panning and zooming - see our quickstart guide.
Learn more...
Here is an example of replacing Paper with PaperScroller from one of our examples, where the container HTML element has id="paper"
(replace with whatever id is used in your code):
// change this... (Paper version):
const namespace = shapes;
const graph = new dia.Graph({}, { cellNamespace: namespace });
const paper = new dia.Paper({
el: document.getElementById('paper'),
model: graph,
width: 300,
height: 300,
background: { color: '#F5F5F5' },
cellViewNamespace: namespace
});
// ...into this (PaperScroller version):
const namespace = shapes;
const graph = new dia.Graph({}, { cellNamespace: namespace })
const paper = new dia.Paper({
model: graph,
width: 2000,
height: 2000,
background: { color: '#F5F5F5' },
cellViewNamespace: namespace
});
const paperScroller = new ui.PaperScroller({
paper: paper,
scrollWhileDragging: true
});
document.getElementById('paper').appendChild(paperScroller.render().el);
Here is an example of the handler methods for the relevant paper events:
paper.on({
'blank:pointerdown': function(evt, x, y) {
paperScroller.startPanning(evt, x, y);
},
'paper:pan': function(evt, tx, ty) {
evt.preventDefault();
paperScroller.el.scrollLeft += tx;
paperScroller.el.scrollTop += ty;
},
'paper:pinch': function(_evt, ox, oy, scale) {
// the default is already prevented
const zoom = paperScroller.zoom();
paperScroller.zoom(zoom * scale, { min: 0.2, max: 5, ox, oy, absolute: true });
}
});