Skip to main content

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:

index.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:

index.js
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.

info

The width and height of the source Paper must be a number. A CSS value such as '100%' will result in PaperScroller: paper dimension must be a number exception.

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.

info

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

info

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.

info

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.

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:

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 by rect (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 given point 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 associated opt.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.
tip

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 });
}
});