Skip to main content
Version: 4.1

Halo

JointJS+ provides a Halo plugin that enables the ability to create a control panel above an element with various tools.

Installation​

Access Halo via the ui namespace, create an instance. Then, provide the view of the cell, and call the render() method.

import { dia, shapes, ui } from '@joint/plus';

const graph = new dia.Graph({}, { cellNamespace: shapes });
const paper = new dia.Paper({
el: document.getElementById('paper'),
width: 500,
height: 500,
model: graph,
cellViewNamespace: shapes
});

paper.on('element:pointerup', (elementView) => {
const halo = new ui.Halo({
cellView: elementView,
type: 'overlay'
});
halo.render();
});
There is also a UMD version available

Include joint.ui.halo.js and joint.ui.halo.css in your HTML:

index.html
<link rel="stylesheet" type="text/css" href="joint.ui.halo.css">
<script src="joint.js"></script>
<script src="joint.ui.halo.js"></script>

Access Halo through the joint.ui namespace:

index.js
const graph = new joint.dia.Graph({}, { cellNamespace: joint.shapes });
const paper = new joint.dia.Paper({
el: document.getElementById('paper'),
width: 500,
height: 500,
model: graph,
cellViewNamespace: joint.shapes
});

paper.on('element:pointerup', (elementView) => {
const halo = new joint.ui.Halo({
cellView: elementView,
type: 'overlay'
});
halo.render();
});

How does Halo work?​

Halo provides the user with the ability to create a control panel above an element that contains various useful tools. This gives the user control over their elements via an easily accessible set of actions. The ui.Halo requires the view of a cell we want to display the halo above.

In the following example, a halo is shown immediately on a rectangle element, and upon user interaction with the elements, a halo is also added.

Customizing halo tools​

note

For the purpose of this section, we will use the overlay halo type.

The overlay halo type is a replacement for deprecated surrounding halo type (which is still available for backward compatibility).

The built-in halo tools for elements are remove, resize, rotate, clone, fork, link, and unlink.

You can use the halo as is, with all the tools available, or choose to display only a subset of them. You can also add your own custom tools.

Halo provides three methods for adding, removing and changing tools: addHandle(), removeHandle() and changeHandle().

You can call the removeHandle() method to remove any of the tools just by passing its name:

halo.removeHandle('clone');

You can change halo tool handles by calling the changeHandle() method. For instance, if we want to change the position of the clone tool, we could use:

halo.changeHandle('clone', { position: 'se' });

Last but not least, you can use the addHandle() method to add new tools to your halo:

halo.addHandle({
name: 'my-action'
position: 's',
icon: 'my-action.png'
data: { myData: 'myValue' }
});

halo.on('action:my-action:pointerdown', (evt) => {
evt.stopPropagation();
console.log(evt.data.myData);
alert('My custom action.');
});

In the example above, we added a new tool named my-action, positioned the tool to the south (bottom-center), and used our own icon my-action.png.

When the user clicks on our tool, Halo triggers an event named action:[name]:pointerdown. We can handle the event by listening on the halo object. Similarly, the Halo triggers action:[name]:pointermove, and action:[name]:pointerup events. This gives us a high flexibility in implementing our own actions.

Customizing halo groups​

The overlay halo type allows you to add custom groups, or modify the existing ones.

The built-in groups are nw, n, ne, e, se, s, sw, w (there is also an enum available e.g ui.Halo.HandlePosition.NE).

We can define custom groups using the groups constructor option object. Here's how to create a new group located to the right of the element:

const halo = new ui.Halo({
cellView,
type: 'overlay',
groups: {
'my-group': {
top: '0',
left: 'calc(100% + 5px)',
verticalAlign: 'top',
horizontalAlign: 'left',
trackDirection: 'column',
trackCount: 2,
gap: '4px'
},
},
});

Handles within the same group are arranged in a row or column, depending on the trackDirection property.

Here's a breakdown of the group properties used in the example above:

  • The top and left properties define the position of the group relative to the element
    • 0 means the top/left edge of the element
    • 100% means the bottom/right edge
  • The verticalAlign and horizontalAlign properties define the alignment of the group relative to the position.
  • The trackDirection property defines whether the group is arranged in rows or columns.
  • The trackCount property defines the number of tracks (rows or columns) in the group.
  • The gap property defines the space between handles.

Now, we can move all default handles to our new group.

['unlink', 'fork', 'rotate', 'resize', 'remove', 'clone', 'link'].forEach(name => {
halo.changeHandle(name, { position: 'my-group' });
});

And here's a live example:

Styling halo tools​

Other available group property is the className which allows you to add a custom class to the group. This can be useful for styling the group.

const halo = new ui.Halo({
cellView,
type: 'overlay',
groups: {
'my-group': {
className: 'my-halo-group',
/* ... */
},
},
});
.my-halo-group {
border: 1px solid #000;
background-color: #f1f1f1;
padding: 5px;
}

Using the className property, we can style the group as we like. Here's how to style a group to look like a toolbar.

The className property can also be used to style individual handles.

halo.addHandle({
/* ... */
className: 'my-halo-handle'
});

It's also possible to style halo handles based on the type of element they are displayed for. The halo <div> container stores the type of the element in its data-type attribute. This makes it easy to target a halo for a certain type of element in your CSS. For instance, let's say we want to hide a specific tool only for an element of type "standard.Rectangle". You can do this:

.halo[data-type="standard.Rectangle"] .my-tool { display: none; }

There are several CSS variables available for styling the halo. Here's a list of them:

/* size of the handles */
--jj-halo-handle-width: 20px;
--jj-halo-handle-height: 20px;
/* size of the icons / or text if provided */
--jj-halo-handle-font-size: 18px;
/* number of pixels the info box overlaps the element */
--jj-halo-box-horizontal-overlap: 20px;
/* offset of the info box from the element */
--jj-halo-box-offset: 30px;
halo.el.style.setProperty('--jj-halo-handle-width', '30px');

Custom handles with default actions​

Let's say we want to add custom handle that runs one of the built-in actions. We can do this by using the Halo.getDefaultHandle() class method.

halo.addHandle({
...ui.Halo.getDefaultHandle('fork'),
name: 'my-fork',
icon: './fork.png',
position: ui.Halo.HandlePosition.NW
});

A useful property for when using the multiple default actions is the data property. It enables passing custom data to the makeElement() and makeLink() callbacks.

const halo = new ui.Halo({
/* ... */
makeLink({ data }) {
return new shapes.standard.Link({
attrs: {
line: {
stroke: data.color
}
}
});
}
});

halo.addHandle({
...ui.Halo.getDefaultHandle('link'),
name: 'link-red',
icon: './link-red.png',
position: ui.Halo.HandlePosition.NE,
data: { color: 'red' }
});

Now, what if we want to add custom handles of different colors that run the default fork and link actions and create elements and links with the same color as the handle. Here's the example of how to do this:

Note that we also take use of the hideOnDrag property to hide the halo when the user starts creating links.

It is also possible to use the Halo plugin with links, as the following demo illustrates.

The built-in halo tools for links are remove, and direction.

Pie menu type of Halo​

To display the Halo control panel as a pie menu, just set the type option to 'pie':

new ui.Halo({ cellView: myElementView, type: 'pie' });

You can still add your own actions as you would normally do with the 'overlay' type of Halo.

There could be multiple pie menus on the element at the same time. See the example below:

Toolbar type of Halo​

warning

Deprecated. Use the 'overlay' type instead with custom styling.

To display the Halo control panel as a small toolbar above an element, just set the type option to 'toolbar':

new joint.ui.Halo({ cellView: myElementView, type: 'toolbar' });

You can still add your own actions as you would normally do with the default 'surrounding' type of Halo.