In the first part of the tutorial, we saw a working example of a simple JointJS application:
In the previous section, we focused on making the elements look much more interesting:
In this section, we are going to understand the other crucial building block of JointJS diagrams - links. This is an introduction to links as they appear in the Hello, World! application; more advanced topics are covered later in the tutorial series. We will continue with the code we modified in the previous section:
<!DOCTYPE html>
<html>
<body>
<!-- content -->
<div id="myholder"></div>
<!-- dependencies -->
<script src="node_modules/@joint/core/dist/joint.js"></script>
<!-- code -->
<script type="text/javascript">
var namespace = joint.shapes;
var graph = new joint.dia.Graph({}, { cellNamespace: namespace });
var paper = new joint.dia.Paper({
el: document.getElementById('myholder'),
model: graph,
width: 600,
height: 300, // height had to be increased
gridSize: 10,
drawGrid: true,
background: {
color: 'rgba(0, 255, 0, 0.3)'
},
cellViewNamespace: namespace
});
var rect = new joint.shapes.standard.Rectangle();
rect.position(100, 30);
rect.resize(100, 40);
rect.attr({
body: {
fill: 'blue'
},
label: {
text: 'Hello',
fill: 'white'
}
});
rect.addTo(graph);
var rect2 = new joint.shapes.standard.Rectangle();
rect2.position(400, 30);
rect2.resize(100, 40);
rect2.attr({
body: {
fill: '#2C3E50',
rx: 5,
ry: 5,
strokeWidth: 2
},
label: {
text: 'World!',
fill: '#3498DB',
fontSize: 18,
fontWeight: 'bold',
fontVariant: 'small-caps'
}
});
rect2.addTo(graph);
var link = new joint.shapes.standard.Link();
link.source(rect);
link.target(rect2);
link.addTo(graph);
var rect3 = new joint.shapes.standard.Rectangle();
rect3.position(100, 130);
rect3.resize(100, 40);
rect3.attr({
body: {
fill: '#E74C3C',
rx: 20,
ry: 20,
strokeWidth: 0
},
label: {
text: 'Hello',
fill: '#ECF0F1',
fontSize: 11,
fontVariant: 'small-caps'
}
});
rect3.addTo(graph);
var rect4 = new joint.shapes.standard.Rectangle();
rect4.position(400, 130);
rect4.resize(100, 40);
rect4.attr({
body: {
fill: '#8E44AD',
strokeWidth: 0
},
label: {
text: 'World!',
fill: 'white',
fontSize: 13
}
});
rect4.addTo(graph);
var link2 = new joint.shapes.standard.Link();
link2.source(rect3);
link2.target(rect4);
link2.addTo(graph);
var rect5 = new joint.shapes.standard.Rectangle();
rect5.position(100, 230);
rect5.resize(100, 40);
rect5.attr({
body: {
fill: '#2ECC71',
strokeDasharray: '10,2'
},
label: {
text: 'Hello',
fill: 'black',
fontSize: 13
}
});
rect5.addTo(graph);
var rect6 = new joint.shapes.standard.Rectangle();
rect6.position(400, 230);
rect6.resize(100, 40);
rect6.attr({
body: {
fill: '#F39C12',
rx: 20,
ry: 20,
strokeDasharray: '1,1'
},
label: {
text: 'World!',
fill: 'gray',
fontSize: 18,
fontWeight: 'bold',
fontVariant: 'small-caps',
textShadow: '1px 1px 1px black'
}
});
rect6.addTo(graph);
var link3 = new joint.shapes.standard.Link();
link3.source(rect5);
link3.target(rect6);
link3.addTo(graph);
</script>
</body>
</html>
Working with links is similar to working with elements:
In our case, the three links are instances of
joint.shapes.standard.Link
.
The standard shape library
contains several other ready-made link definitions (e.g. DoubleLink and ShadowLink) that can
be used in your document.
Moreover, advanced users may create their own link definitions instead, by
extending the basic
joint.dia.Link
class.
Our demo showcases the two required Link methods:
link.source()
and
link.target()
- sets the source/target of the link.
To connect the link to an element, pass the element to the function (as we do in our example).
To pinthe link to a fixed point on the paper, pass a
g.Point
(or an object with
x
and y
properties).Other important methods include:
link.clone()
- clones an existing link, including its source, target, vertices, router, connector, attributes,
and labels (attributes and labels are explained in more detail below).link.addTo()
- adds the link to a graph so it can be rendered.The shape of the link can be set with three optional methods:
link.vertices()
- sets the vertices
array of the link.Vertices are user-defined points on the paper that the link should pass through on its way from
source
to target
.
The default is an empty array (meaning that the link navigates from source to target with no
detour).
link.router()
- sets the router
of the link.The router is a function that takes the array of link vertices and adds additional points to it as
necessary to create a route with desired characteristics; for example, the orthogonal
router creates a route consisting of vertical and horizontal lines attached at right angles.
JointJS comes with a collection of pre-defined routers in the
joint.routers
namespace.
Two of the provided routers are smart routers
that are able to navigate around obstacles in their
path.
The default is the normal
router, which returns the array of vertices as route points.
Advanced users may provide their own routers to
support custom routing strategies.
link.connector()
- sets the connector
of the link.The connector is a function in charge of rendering the link's path on the paper.
It takes the array of route points provided by a router and constructs a series of SVGPathElement
path data
commands to create a path with desired characteristics; for example, the rounded
connector creates straight line segments with rounded corners around route points.
JointJS comes with a collection of pre-defined connectors in the
joint.connectors
namespace.
The default is the normal
connector, which connects route points with simple straight lines.
Advanced users may provide their own
connectors to support custom connecting strategies.
Link geometry is also affected by the anchor and connectionPoint methods applied to link source and target, as well as the connectionStrategy set on the paper.
Link styling works analogously to element styling:
link.attr()
- programmatically assigns SVG attributes directly to the SVGElements of the link's markup.
(CSS styles may still be used on top of the styling defined here, and they will have precedence over
these attributes.)When link.attr()
is called with one object as an argument, the keys of the object are
selectors that correspond to SVGElements of the link's markup; each of those can have one or more
attributes defined alongside the value to be set.
If you only need to change one value, you can also call link.attr()
with two arguments;
the first is the path of the attribute in the form 'selector/attribute'
and the second is
the value to be assigned.
If you are completely new to SVG, you may want to have a look at, for example, the Fills and Strokes tutorial on MDN. JointJS is able to handle all standard SVG attributes, however, please note that we strongly encourage everyone to use camelCase versions of attribute names for consistency and in order to avoid the need for quotation marks in attribute name keys. In addition, JointJS provides an extensive set of non-standard special JointJS attributes; these allow you to specify attributes relatively to other shape selectors, among other things. Special attributes are discussed in detail later in the tutorial.
The
joint.shapes.standard.Link
shape used in our example has two selectors defined:
line
(the visible <path>
SVGElement of the link), and
wrapper
(a wider, transparent <path>
SVGElement under the
line
that makes it easier to interact with the link).
Other link shapes have their own selector names (although consistency was preserved where applicable,
e.g. with line
); please refer to the joint.shapes.standard
documentation for detailed information.
For example, we can change the color of a link by changing the stroke
color attribute on the
line
selector:
link.attr('line/stroke', 'orange');
JointJS source code: links-attr.js
The same effect can be achieved by passing a single object argument to link.attr
:
link.attr({
line: { // selector for the visible <path> SVGElement
stroke: 'orange' // SVG attribute and value
}
});
Two special attributes are in charge of link arrowheads;
sourceMarker
and
targetMarker
.
(In the case of joint.shapes.standard.Link
, they are defined on the line
selector.)
The 'type'
of the arrowhead can be any valid SVGElement type; we will look at
path
and image
arrowheads specifically, but we will mention the others in
more advanced sections of the tutorial.
The following example shows how to create a link with two simple path
arrowheads.
Notice that if the 'fill'
and 'stroke'
colors are not specified, they are
adopted from the line.stroke
attribute.
The two arrowheads have the same path data commands, despite pointing in opposite directions; this is
because all targetMarker
values are automatically rotated by 180 degrees.
The path commands' coordinate system has origin at the tip of the link and is rotated
according to the slope of the link at that point.
Together, these characteristics mean that you can design all your arrowheads as if they were pointing
left and towards the point 0,0
in local coordinates; JointJS will take care of the
rest.
Let's illustrate with a simple clock:
link.attr({
line: {
sourceMarker: { // hour hand
'type': 'path',
'd': 'M 20 -10 0 0 20 10 Z'
},
targetMarker: { // minute hand
'type': 'path',
'stroke': 'green',
'stroke-width': 2
'fill': 'yellow',
'd': 'M 20 -10 0 0 20 10 Z'
}
}
});
JointJS source code: links-arrowheads-path.js
Creating image
arrowheads is similarly straightforward.
You just need to provide a url with the path to your image to the 'xlink:href'
property,
and then specify the desired 'width'
and 'height'
.
The image for targetMarker
will be automatically rotated by 180 degrees, as expected.
Remember to reposition the image in the 'y'
axis if you need the arrowhead to be
centered.
Let's return to our clock example:
link.attr({
line: {
sourceMarker: {
'type': 'image',
'xlink:href': 'https://cdn3.iconfinder.com/data/icons/49handdrawing/24x24/left.png',
'width': 24,
'height': 24,
'y': -12
},
targetMarker: {
'type': 'image',
'xlink:href': 'https://cdn3.iconfinder.com/data/icons/49handdrawing/24x24/left.png',
'width': 24,
'height': 24,
'y': -12
}
}
});
JointJS source code: links-arrowheads-image.js
JointJS also supports link labels:
link.labels()
- sets the labels
array of the link.
Labels have markup
, size
, attrs
, and position
properties.Link labels are explained in more detail in a separate section of this
tutorial.
A simple label
definition (including markup, attrs and position) is built into the joint.dia.Link
class, from which the joint.shapes.standard.Link
type inherits it.
The built-in default label markup contains two subelements:
<text>
SVGElement ('text'
selector) for label text, and
<rect>
SVGElement ('rect'
selector) for label background.
Meanwhile, the built-in default attributes specify that the label should look like a simple vertically
centered text on a white rounded rectangle.
Finally, the built-in default position places the label at the midpoint of the link.
Thus, adding a label can be as simple as passing a value for the text/text
attribute:
link.labels([{
attrs: {
text: {
text: 'Hello, World'
}
}
}]);
JointJS source code: links-label-builtin.js
Note that since we were only adding one label, we could have also used the
link.appendLabel()
convenience function:
link.appendLabel({
attrs: {
text: {
text: 'Hello, World'
}
}
});
Now, let's use what we learned to have some fun with our links:
<!DOCTYPE html>
<html>
<body>
<!-- content -->
<div id="myholder"></div>
<!-- dependencies -->
<script src="node_modules/@joint/core/dist/joint.js"></script>
<!-- code -->
<script type="text/javascript">
var namespace = joint.shapes;
var graph = new joint.dia.Graph({}, { cellNamespace: namespace });
var paper = new joint.dia.Paper({
el: document.getElementById('myholder'),
model: graph,
width: 600,
height: 300,
gridSize: 10,
drawGrid: true,
background: {
color: 'rgba(0, 255, 0, 0.3)'
},
cellViewNamespace: namespace
});
var rect = new joint.shapes.standard.Rectangle();
rect.position(100, 30);
rect.resize(100, 40);
rect.attr({
body: {
fill: 'blue'
},
label: {
text: 'Hello',
fill: 'white'
}
});
rect.addTo(graph);
var rect2 = new joint.shapes.standard.Rectangle();
rect2.position(400, 30);
rect2.resize(100, 40);
rect2.attr({
body: {
fill: '#2C3E50',
rx: 5,
ry: 5,
strokeWidth: 2
},
label: {
text: 'World!',
fill: '#3498DB',
fontSize: 18,
fontWeight: 'bold',
fontVariant: 'small-caps'
}
});
rect2.addTo(graph);
var link = new joint.shapes.standard.Link();
link.source(rect);
link.target(rect2);
link.attr({
line: {
stroke: 'blue',
strokeWidth: 1,
sourceMarker: {
'type': 'path',
'stroke': 'black',
'fill': 'red',
'd': 'M 10 -5 0 0 10 5 Z'
},
targetMarker: {
'type': 'path',
'stroke': 'black',
'fill': 'yellow',
'd': 'M 10 -5 0 0 10 5 Z'
}
}
});
link.labels([{
attrs: {
text: {
text: 'Hello, World!'
}
}
}]);
link.addTo(graph);
var rect3 = new joint.shapes.standard.Rectangle();
rect3.position(100, 130);
rect3.resize(100, 40);
rect3.attr({
body: {
fill: '#E74C3C',
rx: 20,
ry: 20,
strokeWidth: 0
},
label: {
text: 'Hello',
fill: '#ECF0F1',
fontSize: 11,
fontVariant: 'small-caps'
}
});
rect3.addTo(graph);
var rect4 = new joint.shapes.standard.Rectangle();
rect4.position(400, 130);
rect4.resize(100, 40);
rect4.attr({
body: {
fill: '#8E44AD',
strokeWidth: 0
},
label: {
text: 'World!',
fill: 'white',
fontSize: 13
}
});
rect4.addTo(graph);
var link2 = new joint.shapes.standard.Link();
link2.source(rect3);
link2.target(rect4);
link2.vertices([
new g.Point(250, 100),
new g.Point(300, 150),
new g.Point(350, 200)
]);
link2.router('orthogonal');
link2.connector('rounded');
link2.attr({
line: {
stroke: 'gray',
strokeWidth: 4,
strokeDasharray: '4 2',
sourceMarker: {
'type': 'image',
'xlink:href': 'https://cdn3.iconfinder.com/data/icons/49handdrawing/24x24/left.png',
'width': 24,
'height': 24,
'y': -12
},
targetMarker: {
'type': 'image',
'xlink:href': 'https://cdn3.iconfinder.com/data/icons/49handdrawing/24x24/left.png',
'width': 24,
'height': 24,
'y': -12
}
}
});
link2.addTo(graph);
var link3 = new joint.shapes.standard.Link();
link3.source(rect3);
link3.target(rect4);
link3.connector('jumpover', { size: 10 });
link3.addTo(graph);
var rect5 = new joint.shapes.standard.Rectangle();
rect5.position(100, 230);
rect5.resize(100, 40);
rect5.attr({
body: {
fill: '#2ECC71',
strokeDasharray: '10,2'
},
label: {
text: 'Hello',
fill: 'black',
fontSize: 13
}
});
rect5.addTo(graph);
var rect6 = new joint.shapes.standard.Rectangle();
rect6.position(400, 230);
rect6.resize(100, 40);
rect6.attr({
body: {
fill: '#F39C12',
rx: 20,
ry: 20,
strokeDasharray: '1,1'
},
label: {
text: 'World!',
fill: 'gray',
fontSize: 18,
fontWeight: 'bold',
fontVariant: 'small-caps',
textShadow: '1px 1px 1px black'
}
});
rect6.addTo(graph);
var link4 = new joint.shapes.standard.Link();
link4.source(rect5);
link4.target(rect6);
link4.attr({
line: {
stroke: '#3498DB',
strokeWidth: 3,
strokeDasharray: '5 5',
strokeDashoffset: 7.5,
sourceMarker: {
'type': 'path',
'stroke': 'none',
'fill': '#3498DB',
'd': 'M 20 -10 0 0 20 10 Z \
M 40 -10 20 0 40 10 Z'
},
targetMarker: {
'type': 'path',
'stroke': 'none',
'fill': '#3498DB',
'd': 'M 7.5 -10 2.5 -10 2.5 10 7.5 10 Z \
M 17.5 -10 12.5 -10 12.5 10 17.5 10 Z \
M 40 -10 20 0 40 10 Z'
}
}
});
link4.addTo(graph);
</script>
</body>
</html>
JointJS source code: links.js
This concludes the introductory section of the JointJS tutorial! Congratulations! We have come a long way from the initial Hello, World! application:
Now that you have seen the basics, you should be confident working with the most important building blocks of JointJS - Graphs, Papers, Elements, and Links.
The next step is to head over to the intermediate section of the tutorial, which explains some handy but more involved concepts.