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:
<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:
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β
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
andleft
properties define the position of the group relative to the element0
means the top/left edge of the element100%
means the bottom/right edge
- The
verticalAlign
andhorizontalAlign
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.
Links with Haloβ
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β
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
.