Skip to main content

Link model attributes

Beside the model attributes which are common for all shapes, links have its own set of unique model attributes.

Source and target attributes

Links have two crucial model attributes: source and target. They define the starting point and the end point of the link. They can be defined with a Cell id (optionally, with additional subelement/magnet/port reference) or with a Point:

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

// `shapes.standard.Link` inherits from `dia.Link` (`dia.Link` is an abstract class that has no SVG markup defined)
const link1 = new shapes.standard.Link({
source: { id: sourceId },
target: { id: targetId, port: portId }
});

const link2 = new shapes.standard.Link({
source: { id: sourceId },
target: { x: 100, y: 100 }
});

If the end is specified as a Cell, a specific subelement on that cell may be identified for use as the link end. The selector property allows specifying the subelement with a selector string, while the magnet property uses magnet id, and the port property (on elements only) uses the port id.

link.source(rect, {
selector: 'connectorSquare'
});
link.source(link2, {
selector: 'midPointCircle'
});

The source and target attributes accept additional modifier properties that modify the actual position of the link end: anchor/linkAnchor, and connectionPoint.

In any case, we need to obtain a single point from the object provided to the source/target attribute. That point is found according to additional properties provided to the function. The accepted modifier properties depend on the class of the provided object:

  • Point or an object with x and y properties - coordinates of the point are used as the coordinates of the link's anchor directly. No modification of any kind. (Ignores anchor/linkAnchor/connectionPoint properties, if provided.)
  • Element - the precise position of the link's end anchor depends on used anchor property, with additional optical modifications applied by connectionPoint property. (Ignores linkAnchor property, if provided.)
  • Link - the precise position of the link's end anchor depends on provided linkAnchor function. (Ignores anchor/connectionPoint properties, if provided.)
  • Link with specified subelement/magnet property - same as Element (see above).

The connectionStrategy paper option is also relevant to mention in this context. It determines what happens to the link end when it is modified due to specific kinds of user interaction.

Anchor

If the link end (source/target) is an Element (or a subelement/magnet of a Link – but not a Link itself), the precise position of the link end's anchor may be specified by the anchor property. Every link has two anchors; one at the source end and one at the target end.

A link end anchor is a point inside a given (sub)element that the link path wants to connect to at that source/target end - if it were not obstructed by the body of the element itself (it is the role of connectionPoints to then take the obstructing end element itself into account). Alongside link vertices, source and target anchors determine the basic link path. Then, it is the job of connectionPoints, routers, and connectors to modify that path to make it look good.

The anchor functions reference the end element to find the anchor point - e.g. the center of the end element, or its top-right corner. Notably, anchor functions also allow you to offset found anchor points by a custom distance in both dimensions from the standard position (e.g. 10 pixels to the right and 20 pixels below the center of an end element). Several pre-made anchors are provided inside the JointJS library in the anchors namespace.

link.source(rect, {
anchor: {
name: 'bottomLeft',
args: {
dx: 20,
dy: -10
}
}
});

There are many built-in anchor functions in JointJS:

  • 'center' - default anchor at center of view bbox.
  • 'modelCenter' - anchor at center of model bbox.
  • 'perpendicular' - anchor that ensures an orthogonal route to the other endpoint.
  • 'midSide' - anchor in the middle of the side of view bbox closest to the other endpoint.
  • 'bottom' - anchor in the middle of the bottom side of view bbox.
  • 'left' - anchor in the middle of the left side of view bbox.
  • 'right' - anchor in the middle of the right side of view bbox.
  • 'top' - anchor in the middle of the top side of view bbox.
  • 'bottomLeft' - anchor at the bottom-left corner of view bbox.
  • 'bottomRight' - anchor at the bottom-right corner of view bbox.
  • 'topLeft' - anchor at the top-left corner of view bbox.
  • 'topRight' - anchor at the top-right corner of view bbox.

If an anchor property is not provided, the defaultAnchor paper option is used instead. The anchors.center anchor is used by default. You can change the default anchor like this:

paper.options.defaultAnchor = {
name: 'midSide',
args: {
rotate: true,
padding: 20
}
};

In case if the end shape is a link, JointJS looks at linkAnchor property instead.

Custom anchor

New anchor functions can be defined in the anchors namespace (e.g. anchors.myAnchor) or passed directly as a function to the anchor property of link source/target (or to the defaultAnchor option of a paper).

In either case, the anchor function must return the anchor as a Point. The function is expected to have the following signature:

(endView: dia.ElementView, endMagnet: SVGElement, anchorReference: g.Point, args: object) => g.Point:
endViewdia.ElementViewThe CellView to which we are connecting. The Element model can be accessed as endView.model; this may be useful for writing conditional logic based on element attributes.
endMagnetSVGElementThe SVGElement in our page that contains the magnet (element/subelement/port) to which we are connecting.
anchorReferenceg.PointA reference to another component of the link path that may be necessary to find this anchor point. If we are calling this method for a source anchor, it is the first vertex, or if there are no vertices the target anchor. If we are calling this method for a target anchor, it is the last vertex, or if there are no vertices the source anchor...
SVGElement...if the anchor in question does not exist (yet), it is that link end's magnet. (The built-in methods usually use this element's center point as reference.)
argsobjectAn object with additional optional arguments passed to the anchor method by the user when it was called (the args property).

If the link end (source/target) is a Link (not an Element or a Link subelement/magnet), the precise position of the link end's anchor may be specified by the linkAnchor property. If a link anchor method is used, connectionPoints are not applied.

A link end link anchor is a point on another link that this link's path wants to connect to at that source/target end. Alongside link vertices, source and target anchors determine the basic link path.

The link anchor functions reference the end link to find the anchor point - e.g. a point at a given ratio from the start, or the closest point. Several pre-made link anchors are provided inside the JointJS library in the linkAnchors namespace.

link.source(link2, {
linkAnchor: {
name: 'connectionRatio',
args: {
ratio: 0.25
}
}
});

There are several built-in linkAnchor functions in JointJS:

If a link anchor function is not provided, the defaultLinkAnchor paper option is used instead. The linkAnchors.connectionRatio link anchor with a value of 0.5 is used by default (i.e. the anchor is placed at the midpoint of a Link by default). You can change the default link anchor in the following manner:

paper.options.defaultLinkAnchor = {
name: 'connectionLength',
args: {
length: 20
}
};

If the reference object is an Element, JointJS looks at anchor property instead.

New link anchor functions can be defined in the linkAnchors namespace (e.g. linkAnchors.myLinkAnchor) or passed directly as a function to the linkAnchor property of link source/target (or to the defaultLinkAnchor option of a paper).

In either case, the link anchor function must return the link anchor as a Point. The function is expected to have the following signature:

(endView: dia.LinkView, endMagnet: null, anchorReference: g.Point, args: object) => g.Point;
endViewdia.LinkViewThe LinkView to which we are connecting. The Link model can be accessed as endView.model; this may be useful for writing conditional logic based on link attributes.
endMagnetnull(Not used) This argument is only present to ensure that the signature of custom linkAnchor methods is the same as the signature of a custom anchor.
anchorReferenceg.PointA reference to another component of the link path that may be necessary to find this anchor point. If we are calling this method for a source anchor, it is the first vertex; if there are no vertices, it is the target anchor. If we are calling this method for a target anchor, it is the last vertex; if there are no vertices, it is the source anchor...
SVGElement...if the anchor in question does not exist (yet), it is that link end's magnet. (The built-in methods usually use this element's center point as reference.)
argsobjectAn object with additional optional arguments passed to the link anchor method by the user when it was called (the args property).

Connection point

The link end's connection point may be specified by the connectionPoint property. Every link has two connection points; one at the source end and one at the target end.

A link connection point is the point at which the link path actually ends at, taking the end element into account. This point will always lie on the link path (the line connecting link anchors and vertices together, in order).

The connection points are found by considering intersections between the link path and a desired feature of the end element (e.g. bounding box, shape boundary, anchor). Although connection points are not capable of being offset off the link path (anchors should be used to modify the link path if this is required), they can be offset along the path - e.g. to form a gap between the element and the actual link. Several pre-made connection points are provided in the JointJS library in the connectionPoints namespace. However, the functions always only have access to a single path segment; the source connectionPoint is found by investigating the first segment (i.e. source anchor - first vertex, or source anchor - target anchor if there are no vertices), while the target connectionPoint is found by investigating the last segment (i.e. last vertex - target anchor, or source anchor - target anchor). This has consequences if the investigated path segment is entirely contained within the end element.

link.source(rect, {
connectionPoint: {
name: 'boundary',
args: {
offset: 5
}
}
});

There are four built-in connection point functions in JointJS:

  • 'anchor' - connection point at anchor.
  • 'bbox' - default connection point at bbox boundary.
  • 'boundary' - connection point at actual shape boundary.
  • 'rectangle' - connection point at unrotated bbox boundary.

If a connection point property is not provided, the defaultConnectionPoint paper option is used instead. The connectionPoints.bbox connection point is used by default. Here is the example of changing the default connection point:

paper.options.defaultConnectionPoint = {
name: 'boundary',
args: {
sticky: true
}
};

Custom connection point

New connection point function can be defined in the connectionPoints namespace (e.g. connectionPoints.myConnectionPoint) or passed directly as a function to the connectionPoint property of link source/target (or to the defaultConnectionPoint option of a paper).

In either case, the connection point function must return the connection point as a Point. The function is expected to have the following signature:

(endPathSegmentLine: g.Line, endView: dia.ElementView, endMagnet: SVGElement, args: object) => g.Point;
endPathSegmentLineg.LineThe link path segment at which we are finding the connection point. If we are calling this method for a source connection point, it is the first segment (source anchor - first vertex, or source anchor - target anchor). If we are calling this method for a target connection point, it is the last segment (last vertex - target anchor, or source anchor - target anchor).
endViewdia.ElementViewThe ElementView to which we are connecting. The Element model can be accessed as endView.model; this may be useful for writing conditional logic based on element attributes.
endMagnetSVGElementThe SVGElement in our page that contains the magnet (element/subelement/port) to which we are connecting.
argsobjectAn object with additional optional arguments passed to the connection point method by the user when it was called (the args property).

Connection strategy

Connection strategies come into play when the user modifies the position of link endpoints. There are two situations in which this is relevant:

  • When the user drags a link endpoint and connects it to an element or its port. The connection strategy determines the end anchor after the user is finished dragging the link endpoint. (Note that any individual anchor property that might have been assigned on the dragged link endpoint will be overridden by the connection strategy).
  • When a user creates a link, for example by clicking a port. The connection strategy determines the new link's source anchor.

There are three built-in connection strategies in JointJS:

  • useDefaults - default strategy; ignore user pointer and use default anchor and connectionPoint functions.
  • pinAbsolute - use user pointer coordinates to position anchor absolutely within end element; use default connectionPoint.
  • pinRelative - use user pointer coordinates to position anchor relatively within end element; use default connectionPoint.

Both the anchor and connectionPoint properties are rewritten in response to user interaction. None of the built-in connection strategies preserve the originally assigned anchor and connection point functions. To assign precisely what you need as the anchor and connection point functions, you may need to define your own custom connection strategy.

Connection strategies are not assigned on a link-by-link basis. They are set with the connectionStrategy option on a paper.

The default connection strategy is specified as null in paper settings, which is equivalent to connectionStrategies.useDefaults.

Built-in connection strategies are specified by reference to their their name in the connectionStrategies namespace. Example:

import { connectionStrategies } from '@joint/core';

paper.options.connectionStrategy = connectionStrategies.pinAbsolute;

If a connection strategy is not provided, the connectionStrategies.useDefaults strategy is used by default.

Custom connection strategy

New connection strategies can be defined in the connectionStrategies namespace (e.g. connectionStrategies.myConnectionStrategy) or passed directly as a function to the connectionStrategy option of a paper.

In either case, the connection strategy function must return an end definition (i.e. an object in the format supplied to the link.source() and link.target() functions). The function is expected to have the following signature:

(endDefinition: object, endView: dia.ElementView, endMagnet: SVGElement, coords: g.Point) => object;
endDefinitionobjectAn end definition; the output of the appropriate end function (link.source() or link.target()). An object containing at least an id of the Element to which we are connecting. This object is expected to be changed by this function and then sent as the return value.
endViewdia.ElementViewThe ElementView to which we are connecting. The Element model can be accessed as endView.model; this may be useful for writing conditional logic based on element attributes.
endMagnetSVGElementThe SVGElement in our page that contains the magnet (element/subelement/port) to which we are connecting.
coordsg.PointA Point object recording the x-y coordinates of the user pointer when the connection strategy was invoked.

Custom connection strategies may be enormously useful for your users. Here we provide some examples of custom functionality.

Connecting to Ancestors

If your diagram makes heavy use of nested elements, it may be useful to always connect links to a top-level ancestor element (instead of the element on which the arrowhead was actually dropped by user interaction):

import { connectionStrategies } from '@joint/core';

connectionStrategies.topAncestor = (end, endView) => {

const ancestors = endView.model.getAncestors();
const numAncestors = ancestors.length;
end = numAncestors ? ancestors[numAncestors - 1] : end;

return end;
}

paper.options.connectionStrategy = connectionStrategies.topAncestor;
Connecting to Ports

If your diagram uses ports, you usually do not want links to be able to connect anywhere else. The solution is similar to the one above:

import { connectionStrategies } from '@joint/core';

connectionStrategies.firstPort = (end, endView) =>{

const ports = endView.model.getPorts();
const numPorts = ports.length;
end = numPorts ? { id: end.id, port: ports[0].id } : end;

return end;
}

paper.options.connectionStrategy = connectionStrategies.firstPort;
Replicating Built-in Anchor Functions

Furthermore, it is very easy to replicate the built-in anchor functions for connection strategy scenarios - simply apply the anchor function to the received end parameter:

import { connectionStrategies } from '@joint/core';

connectionStrategy.midSide = (end) => {

end.anchor = {
name: 'midSide',
args: {
rotate: true
}
};

return end;
}

paper.options.connectionStrategy = connectionStrategy.midSide;
Replicating Built-in Connection Point Functions

What if we needed to replicate a built-in connection point function instead? We use the same idea as in the previous example:

import { connectionStrategies } from '@joint/core';

connectionStrategy.boundary = (end) => {

end.connectionPoint = {
name: 'boundary',
args: {
offset: 5
}
};

return end;
}

paper.options.connectionStrategy = connectionStrategy.boundary;

Of course, it is also possible to combine both of the examples and assign an anchor as well as connectionPoint to the end parameter of the modified link.

Vertices

The vertices attribute is an array of user-defined points for the link to pass through. Alongside the source and target anchors, the link vertices determine the basic link path. This skeleton path is then used for determining the link route using router. The vertices can be accessed with the link.vertices() function and related functions.

link.vertices();
link.vertices([{ x: 100, y: 120 }, { x: 150, y: 60 }]);

Router

Routers take an array of link vertices and transform them into an array of route points that the link should go through. The difference between vertices and the route is that the vertices are user-defined while the route is computed. The route inserts additional private vertices to complement user vertices as necessary (e.g. to make sure the route is orthogonal). This route is then used for generating the connection SVG path commands using connector. The router attribute of a link can be accessed with the link.router() function.

A collection of pre-made routers is provided inside the JointJS library in the routers namespace. This includes "smart routers" that are able to automatically avoid obstacles (elements) in their way.

There are five built-in routers:

link.router('manhattan', {
excludeEnds: ['source'],
excludeTypes: ['myNamespace.MyCommentElement'],
startDirections: ['top'],
endDirections: ['bottom']
});

The 'manhattan' and 'metro' routers are our smart routers; they automatically avoid obstacles (elements) in their way.

The 'orthogonal', 'manhattan' and 'rightAngle' routers generate routes consisting exclusively of vertical and horizontal segments. The 'metro' router generates routes consisting of orthogonal and diagonal segments.

note

Modular architecture of JointJS allows mixing-and-matching routers with connectors as desired; for example, a link may be specified to use the 'jumpover' connector on top of the 'manhattan' router:

const link = new shapes.standard.Link();
link.source(rect);
link.target(rect2);
link.router('manhattan');
link.connector('jumpover');

If a router is not provided, the defaultRouter paper option is used instead. The routers.normal router is used by default. You can change the default router like this:

paper.options.defaultRouter = {
name: 'orthogonal',
args: {
padding: 10
}
};

Custom router

New routers can be defined in the routers namespace (e.g. routers.myRouter) or passed directly as a function to the router property of a link (or to the defaultRouter option of a paper).

In either case, the router function must return an array of route points that the link should go through (not including the start/end connection points). The function is expected to have the following signature:

(vertices: Array<g.Point>, args: object, linkView: dia.LinkView) => Array<g.Point>;
verticesArray<g.Point>The vertices of the route.
argsobjectAn object with additional optional arguments passed to the router method by the user when it was called (the args property).
linkViewdia.LinkViewThe LinkView of the connection. The Link model can be accessed as the model property; this may be useful for writing conditional logic based on link attributes.

Example of a router defined in the routers namespace:

import { routers, g, shapes } from '@joint/core';

routers.randomWalk = (vertices, args, linkView) => {

const NUM_BOUNCES = args.numBounces || 20;

vertices = joint.util.toArray(vertices).map(g.Point);

for (let i = 0; i < NUM_BOUNCES; i++) {

const sourceCorner = linkView.sourceBBox.center();
const targetCorner = linkView.targetBBox.center();

const randomPoint = g.Point.random(sourceCorner.x, targetCorner.x, sourceCorner.y, targetCorner.y);
vertices.push(randomPoint)
}

return vertices;
}

const link = new shapes.standard.Link();
link.source(source);
link.target(target);

link.router('randomWalk', {
numBounces: 10
});

An example of a router passed as a function is provided below. Notice that this approach does not enable passing custom args nor can it be serialized with the graph.toJSON() function.

import { util, g, shapes } from '@joint/core';

const link = new shapes.standard.Link();
link.source(source);
link.target(target);

link.router((vertices, args, linkView) => {

const NUM_BOUNCES = 20;

vertices = util.toArray(vertices).map(g.Point);

for (let i = 0; i < NUM_BOUNCES; i++) {

const sourceCorner = linkView.sourceBBox.center();
const targetCorner = linkView.targetBBox.center();

const randomPoint = g.Point.random(sourceCorner.x, targetCorner.x, sourceCorner.y, targetCorner.y);
vertices.push(randomPoint)
}

return vertices;
});

Connector

Connectors take an array of link route points and generate SVG path commands so that the link can be rendered. The connector attribute of a link can be accessed with the link.connector() function.

A collection of pre-made connectors is provided inside the JointJS library in the connectors namespace.

There are six built-in connectors in JointJS:

  • 'straight' - connector with straight lines and different ways to handle corners (point, rounded, bevelled, gap).
  • 'jumpover' - connector with angled straight lines and bridges over link intersections.
  • 'normal' - (deprecated) default connector with angled straight lines.
  • 'rounded' - (deprecated) connector with straight lines and rounded edges.
  • 'smooth' - connector interpolated as a bezier curve.
  • 'curve' - connector interpolating as a curve defined by ending tangents.
link.connector('straight', {
cornerType: 'cubic',
cornerRadius: 20
});

The default connector is 'normal'; this can be changed with the defaultConnector paper option. Example:

paper.options.defaultConnector = {
name: 'straight',
args: {
cornerType: 'line',
cornerRadius: 20
}
}

Custom connector

New connectors can be defined in the connectors namespace (e.g. connectors.myConnector) or passed directly as a function to the connector property of a link (or to the defaultConnector paper option).

In either case, the connector function must return a g.Path representing the SVG path data that will be used to render the link. The function is expected to have the following signature:

(sourcePoint: g.Point, targetPoint: g.Point, routePoints: Array<g.Point>, args: object) => g.Path;
sourcePointg.PointThe source connection point.
targetPointg.PointThe target connection point.
routePointsArray<g.Point>The points of the route, as returned by the router in use.
argsobjectAn object with additional optional arguments passed to the connector method by the user when it was called (the args property).

Example of a connector defined in the connectors namespace:

import { connectors, g, shapes } from '@joint/core';

connectors.wobble = (sourcePoint, targetPoint, vertices, args) => {

const SPREAD = args.spread || 20;

const points = vertices.concat(targetPoint)
const prev = sourcePoint;
const path = new g.Path(g.Path.createSegment('M', prev));

const n = points.length;
for (let i = 0; i < n; i++) {

const next = points[i];
const distance = prev.distance(next);

let d = SPREAD;
while (d < distance) {
const current = prev.clone().move(next, -d);
current.offset(
Math.floor(7 * Math.random()) - 3,
Math.floor(7 * Math.random()) - 3
);
path.appendSegment(g.Path.createSegment('L', current));
d += SPREAD;
}

path.appendSegment(g.Path.createSegment('L', next));
prev = next;
}

return path;
}

const link = new shapes.standard.Link();
link.source(source);
link.target(target);

link.connector('wobble', {
spread: 10
});

An example of a connector passed as a function is provided below. Notice that this approach does not enable passing custom args nor can it be serialized with the graph.toJSON() function.

import { g, shapes } from '@joint/core';

const link = new shapes.standard.Link();
link.source(source);
link.target(target);

link.connector((sourcePoint, targetPoint, vertices, args) => {

const SPREAD = 20;

const points = vertices.concat(targetPoint)
const prev = sourcePoint;
const path = new g.Path(g.Path.createSegment('M', prev));

const n = points.length;
for (let i = 0; i < n; i++) {

const next = points[i];
const distance = prev.distance(next);

let d = SPREAD;
while (d < distance) {
const current = prev.clone().move(next, -d);
current.offset(
Math.floor(7 * Math.random()) - 3,
Math.floor(7 * Math.random()) - 3
);
path.appendSegment(g.Path.createSegment('L', current));
d += SPREAD;
}

path.appendSegment(g.Path.createSegment('L', next));
prev = next;
}

return path;
});