Ports
Many diagramming applications deal with the idea of elements with ports. Ports are often displayed as circles inside diagram elements. They are not only used as "sticky" points for connected links, but they also further structure the linking information. It is common that certain elements have lists of input and output ports. A link might not then point to an element as a whole, but to a certain port instead.
JointJS has built-in support for elements with ports, linking between ports, and a facility for defining what connections are allowed. This is useful if you, for example, want to restrict linking between input and output ports, or between certain ports of different elements. This tutorial takes you through the process from the beginning, and shows you how you can achieve all that.
Creating elements with ports
To add ports to elements, we can pass a ports definition as an option to a constructor, or utilize the Port API provided by JointJS. Both methods allow us to add ports to any arbitrary shape we can imagine.
In the following example, we define a port with some basic configuration, and instantiate a standard.Rectangle
shape. Notice we provide a magnet
attribute with a value of true
to our port definition. This allows our port to become a source/target of a link during reconnection, and provide that nice UI interaction. That's why when we click and drag a port, JointJS automatically creates a link coming out of that port. Cool, right?
When drawing a link from an active magnet, the default link is shapes.standard.Link
. You may wish to change the link which is created. You can achieve this via the defaultLink
paper option as shown in this example. To prevent the link being dropped in a blank paper area, use the linkPinning
option.
What if we want a little more control over our ports? Expanding from our previous example, we will introduce the idea of groups. Groups can provide more structure, allow us to separate ports into the catagories we want, and influence the layout of our ports.
In the following example, we will cover a common use case, the idea of input and output ports. To get started, we create a separate port definition for each group, and again add some basic configuration. This allows us to create groups with similar properties, but differences in visual presentation.
When we are satisfied with our individual port definitions, we can then define our port groups in the element constructor. You are free to name the groups how you like depending on your specific use case. As we are creating input and output ports, we name our port groups 'in'
and 'out'
respectively.
Lastly, using the Port API, we use the addPorts()
method to add an array of ports while also providing a custom label for each one.
Setting port properties
If you are already familiar with the prop()
method for shapes, JointJS also provides a similar method for working with ports. portProp()
allows users to set presentation attributes or custom data properties on an element port.
const port = {
id: 'custom-port-id', // set a custom ID
label: {
markup: [{
tagName: 'text',
selector: 'label'
}]
},
attrs: {
portBody: {
magnet: true,
width: 16,
height: 16,
x: -8,
y: -8,
fill: '#03071E'
},
label: {
text: 'port'
}
},
markup: [{
tagName: 'rect',
selector: 'portBody'
}]
};
const element = new joint.shapes.standard.Rectangle({
position: { x: 275, y: 50 },
size: { width: 90, height: 90 },
attrs: {
body: {
fill: '#8ECAE6'
}
},
ports: {
items: [ port ]
}
});
element.portProp('custom-port-id', 'attrs/portBody', { r: 8, fill: 'darkslateblue' });
const portId = element.getPorts()[0].id;
element.portProp(portId, 'custom', { testAttribute: true });
console.log(element.portProp(portId, 'custom')); // {testAttribute: true}
element.portProp(portId, 'attrs/portBody/fill', 'tomato');
Linking elements with ports
Ports aren't so interesting all by themselves, so let's look at linking elements with ports. In the diagram that follows, we create two elements much like the previous examples. We can observe which ports are connected to which, because JointJS stores information about ports in the link models themselves once the links are created via the UI. Try connecting the ports yourself, and you should see a message displaying the linking information.
Linking restrictions
Now that we know how to create elements with ports, and get the linking information, we should look at another common pattern, and that's how to restrict certain connections. JointJS doesn't limit you, and allows you to restrict or permit any connections you like depending on your use case. JointJS simply allows you to define a function that returns true
if a connection between a source and target magnet of a certain element is allowed, or false
otherwise.
You can also mark certain magnets as 'passive'
. That means JointJS will treat the magnets in a way that doesn't permit them becoming a source of a link. For further information, please see the list of options that you can pass to dia.Paper
in the [API reference page](../../../../../api/dia/Paper//Paper.mdx, especially the two related functions: validateConnection()
and validateMagnet()
.
As we have discussed input and output ports throughout this tutorial, we will continue with that theme. In the following example, we have restricted linking from input ports, linking to output ports, and also linking from output to input ports of the same element. All of this simply means we can link from output to input ports as long as it's a different element.
You can see some example restrictions in the following code snippet and in the example below:
const paper = new joint.dia.Paper({
// Other Paper options
validateConnection: function(cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
// Prevent linking from input ports
if (magnetS && magnetS.getAttribute('port-group') === 'in') return false;
// Prevent linking from output ports to input ports within one element
if (cellViewS === cellViewT) return false;
// Prevent linking to output ports
return magnetT && magnetT.getAttribute('port-group') === 'in';
},
validateMagnet: function(cellView, magnet) {
// Note that this is the default behavior. It is shown for reference purposes.
// Disable linking interaction for magnets marked as passive
return magnet.getAttribute('magnet') !== 'passive';
}
});
Link snapping
To improve user experience, you might want to enable link snapping. This means that while the user is dragging a link, it searches for the closest port in a given radius. Once a suitable port is found (it meets requirements specified in validateConnection()
), the link automatically connects to it. You can try this functionality in the example below:
const paper = new joint.dia.Paper({
// Other Paper options
validateConnection: function(cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
// Prevent loop linking
return (magnetS !== magnetT);
},
// Enable link snapping within 20px lookup radius
snapLinks: { radius: 20 }
});
Marking available magnets
Another way to make the user's life easier is to indicate which magnets are available to connect to while dragging a link. To achieve this, all you have to do is enable the markAvailable
option on the paper, and add some CSS rules in your stylesheet to target the classes shown in the code snippet below.
const paper = new joint.dia.Paper({
// Other Paper options
// Enable mark available for cells & magnets
markAvailable: true
});
/* port styling */
.available-magnet {
fill: #5DA271;
}
/* element styling */
.available-cell rect {
stroke-dasharray: 5, 2;
}
Using the paper highlighting option, JointJS also allows us to customize highlighting behavior relating to a given interaction. In the code snippet below, magnetAvailability
creates a custom CSS class we can target in our stylesheets, and elementAvailability
is used to give our element a nice accent color upon availability. You can also utilize your own custom highlighters in this manner. If you want to know more about highlighters, you should check correspondent section in our documentation.
const paper = new joint.dia.Paper({
// Other paper options
highlighting: {
'magnetAvailability': {
name: 'addClass',
options: {
className: 'custom-available-magnet'
}
},
'elementAvailability': {
name: 'stroke',
options: {
padding: 20,
attrs: {
'stroke-width': 3,
'stroke': '#ED6A5A'
}
}
}
}
});
Port layouts
JointJS provides options to set up layout of ports and port labels. Previously, we used this functionality in linking elements with ports, and several other sections. There we aligned our port groups to the 'left'
and 'right'
of our standard.Rectangle
element. This functionality is very convenient and JointJS provides more layout options out of the box.
Let's explore an example.
In the example diagram , we create a standard.Ellipse
element. The layout we were using before doesn't make much sense for a circular shape if we have more than one port, so we will use a radial layout for our ports instead. In our port definition, notice we now use the 'ellipseSpreads'
layout to evenly spread our input ports along the ellipse.
One important note about port layouts is that they can only be defined at group
level, so it's something to be aware of when you are creating port definitions.
const ellipsePortsIn = {
position: {
// name of the port layout
name: 'ellipseSpread',
args: {
dx: 1,
dy: 1,
dr: 1,
startAngle: 220,
step: 50,
compensateRotation: false
}
},
// Other attributes
};
You can look at the list of the available port layouts in our API reference. Here is the example of different built-in port layouts in action:
Custom port layout
An alternative for built-in layouts is providing a function directly, where the function returns an array of port transformations (position and angle).
// ports definition of an element
ports: {
groups: {
customLayoutGroup: {
position: (ports, elBBox, opt) => {
return ports.map((port, index) => {
const step = -Math.PI / 8;
const y = Math.sin(index * step) * 50;
return new g.Point({
x: index * 12,
y: y + elBBox.height,
});
});
},
// other options
},
},
},
Port label layouts
JointJS provides several built-in layouts for port label positions, which are depending on the port position.
// part of the group definition
label: {
position: {
// name of the port label layout
name : 'right',
args: {
x: 0,
y: 0,
angle: 0,
attrs: {}
}
}
}
You can look at the list of built-in port label layouts in our API reference.
Here is the example with built-in label layouts in action:
Frequently asked questions
How can I make all links connect to a specific side of an element?
You can achieve this with ports. Ports are a powerful and flexible feature of JointJS - see above.