Skip to main content
Version: 4.1

Element

The basic model for diagram elements. It inherits from dia.Cell with a few additional properties and methods specific to elements. For a quick introduction to elements, see our tutorial. To learn about elements and their features in detail, see the elements learn section.

Methods

addPort()

element.addPort(port, [opt])

Add a single port, where port could be defined as described in section Port interface

addPorts()

element.addPorts(ports, [opt])

Add array of ports. Ports are validated for id duplicities, if there is a id collision, no new ports are added, where port could be defined as describe in section Port interface

angle()

element.angle()

Return the rotation of the element in degrees (0° - 360°).

fitEmbeds()

element.fitEmbeds([opt])

An alias for the element.fitToChildren function.

fitParent()

element.fitParent([opt])

Resize and reposition this element's embedding parent element such that all of its children (including this element) end up within the parent's new bounding box.

Starting from a given element, this function proceeds upwards to that element's parent (and further ancestors, if opt.deep is used). In that sense, this function is the opposite of the element.fitToChildren function.

Available options:

paddingnumberInflate the embedding parent element's calculated bounding box by this much additional padding.
expandOnlybooleanIf true, the algorithm is only ever allowed to expand the bounding box of the embedding parent element, never to shrink it. You can visualize this setting as the algorithm dragging the top-left and bottom-right corners of the parent's bounding box outward until its children (including this element) are within the bounding box.
shrinkOnlyboolean

If true, the algorithm is only ever allowed to shrink the bounding box of the embedding parent element, never to expand it. You can visualize this setting as the algorithm dragging the top-left and bottom-right corners of the parent's bounding box inward until only its children (including this element) are within the bounding box.

If only a portion of this element (or this element's sibling element) initially overlaps the embedding parent element, the parent's calculated bounding box will only include that portion. If there is no overlap between this element and its parent (i.e. this element is outside the parent), then this function does nothing (since satisfying the shrink-only restriction is impossible).

deepboolean

If true, this algorithm is applied recursively on the embedding parent element of this element's embedding parent element, and so on. The bounding box algorithm is evaluated in reverse-depth order - starting from this element, then going up (i.e. to the topmost embedding ancestor element - or opt.terminator), to make sure that any paddings applied on lower-level elements are taken into account by higher-level elements.

Note that if this option is used in conjunction with opt.shrinkOnly, the algorithm is still applied strictly recursively (i.e. one level at a time). Therefore, even if this element lies completely within the current bounding box of its grandfather element, the grandfather element's calculated bounding box will shrink only based on this element's parent - which may mean that this element will end up outside of the calculated bounding box of the grandfather element.

terminatorCell | Cell.ID

If opt.deep is true and a Cell reference or a Cell ID is provided as a value of this option, then the specified element is the last one for which the bounding box algorithm is applied during recursion - it is the last element whose bounding box is resized and repositioned based on the bounding boxes of its children.

Handling of edge cases:

  • If the provided value refers to this element, then nothing happens.
  • If the provided value refers to this element's embedding parent element, then the result is the same as calling the function without opt.deep and opt.terminator.
  • If the provided value does not refer to an ancestor of this element, then the result is the same as calling the function without opt.terminator.
  • If the provided value does not refer to a Cell of the Element type, then the result is the same as calling the function without opt.terminator.

fitToChildren()

element.fitToChildren([opt])

Resize and reposition this element such that all of its embedded child elements end up within this element's new bounding box.

Starting from a given element, this function proceeds downwards through that element's children (and further descendants, if opt.deep is used). In that sense, this function is the opposite of the element.fitParent function.

Available options:

paddingnumberInflate this element's calculated bounding box by this much additional padding.
expandOnlybooleanIf true, the algorithm is only ever allowed to expand the bounding box of this element, never to shrink it. You can visualize this setting as the algorithm dragging the top-left and bottom-right corners of this element's bounding box outward until all its embedded child elements are within the bounding box.
shrinkOnlyboolean

If true, the algorithm is only ever allowed to shrink the bounding box of this element, never to expand it. You can visualize this setting as the algorithm dragging the top-left and bottom-right corners of this element's bounding box inward until only its embedded child elements are within the bounding box.

If only a portion of an embedded child element initially overlaps this element, the calculated bounding box will only include that portion. If there is no overlap between this element and its children (i.e. all children are currently placed outside of this element), then this function does nothing (since satisfying the shrink-only restriction is impossible).

deepboolean

If true, this algorithm is applied recursively on all embedded children of this element which have embedded children of their own, and so on. The bounding box algorithm is evaluated in reverse-depth order - starting from the deepest descendant, then going up (i.e. to this element). This ensures that any paddings applied on lower-level elements are taken into account by higher-level elements.

Note that if this option is used in conjunction with opt.shrinkOnly, the algorithm is still applied strictly recursively - one level at a time. Therefore, even if this element's current bounding box completely overlaps one of its grandchild elements, this element's calculated bounding box will shrink only based on the grandchild's parent (this element's child), which may mean that the grandchild element will end up outside of the calculated bounding box of this element.

getAbsolutePointFromRelative()

element.getAbsolutePointFromRelative(x, y)
element.getAbsolutePointFromRelative(relativePoint)

Accept a point in the coordinate system of the element and return the point in the graph coordinate system, represented as a g.Point.

The element coordinate system has its origin [0,0] at the element.position() rotated by the element.angle(). Each axis x and y are rotated by the same angle.

getBBox()

element.getBBox([opt])

Return the bounding box of the element model, represented as a g.Rect.

Keep in mind that this function reports the dimensions of the element's model, not the dimensions of the element's view. (For example, the model bounding box does not include any associated ports). See the documentation of the elementView.getBBox function for details about the differences.

if (element1.getBBox().intersect(element2.getBBox())) {
// intersection of the two elements
}
optdescription
deepReturn the union of the element's bounding box and all the elements embedded into it.
rotateReturn the bounding box after the element's rotation.

getGroupPorts()

element.getGroupPorts(groupName)

Returns an array of all ports on the element with the given groupName. If there is no such a port an empty array is returned.

getPort()

element.getPort(id)

Returns the port specified by an id. If such a port does not exist the method returns undefined.

getPortGroupNames()

element.getPortGroupNames(): string[]

Returns an array of port group names of the element. It returns all port group names defined on the element regardless of whether the port group has any ports or not.

element.getPortGroupNames().forEach(groupName => {
const groupPortPositions = element.getPortsPositions(groupName);
/* ... */
});

getPortIndex()

element.getPortIndex(portId)

Returns the port index in the array of port items.

getPorts()

element.getPorts()

Returns an array of all ports on the element. If there is no port an empty array is returned.

getPortsPositions()

element.getPortsPositions(groupName)

Returns the positions and the angle of all ports in the group, relatively to the element position.

getRelativePointFromAbsolute()

element.getRelativePointFromAbsolute(x, y)
element.getRelativePointFromAbsolute(absolutePoint)

Accept a point in the graph coordinate system and return the point in the coordinate system of the element, represented as a g.Point.

The element coordinate system has its origin [0,0] at the element.position() rotated by the element.angle(). Each axis x and y are rotated by the same angle.

hasPort()

element.hasPort(id)

Check if an element contains a port with id. Returns boolean.

hasPorts()

element.hasPorts()

Check if an element has any ports defined. Returns boolean.

insertPort()

element.insertPort(before, port, [opt])

Insert a new port before another port, where port could be defined as described in section Port interface and before is either an index, port id or port itself.

isElement()

element.isElement()

Always returns true. See cell.isElement().

element.isLink()

Always returns false. See cell.isLink().

portProp()

element.portProp(portId, path, [value])

Set properties, possibly nested, on the element port. This is an equivalent of the attr() method but this time for custom data properties.

element.portProp('port-id', 'attrs/circle/fill', 'red');
element.portProp('port-id', 'attrs/circle/fill'); // 'red'

element.portProp('port-id', 'attrs/circle', { r: 10, stroke: 'green' });
element.portProp('port-id', 'attrs/circle'); // { r: 10, stroke: 'green', fill: 'red' }

position()

element.position([opt])

If position() is called without arguments, it returns the current position.

OptionDefaultDescription
parentRelativefalseThe method returns the current position of the element relative to its parent.
element.position(x, y, [opt])

Set the element position to x and y coordinates. This is almost equivalent to element.set('position', { x, y }, opt). However, this method provides some additional functionality.

OptionDefaultDescription
parentRelativefalseIf set to true the x and y coordinates will be treated relatively to the parent element of this element. If the element has no parent or the parent is a link, the option is ignored.
deepfalseIf set to true it will position the element and its descendants while keeping the original distances of the descendants to/from the element origin. The relative distances of the descendants are maintained.
restrictedAreanull

If set to a rectangle, the translation of the element will be restricted to that rectangle only. The restrictedArea is an object of the form { x: Number, y: Number, width: Number, height: Number }. This is useful, e.g. if you want to restrict the translation of an embedded element within its parent. The only thing you have to do in this case is to pass the bounding box of the parent element to the restrictedArea option:

myElement.position(x, y, { restrictedArea: myElement.getParentCell().getBBox() })

The code above makes sure that the element myElement never crosses the bounding box of its parent element. Note that this also works if the element myElement has other embedded elements. In other words, the bounding box of the myElement that is used to calculate the restriction is the total bounding box, including all its children (in case they are sticking out).

el1.position(100, 100);
el1.embed(el2);
el2.position(10, 10, { parentRelative: true });
el2.position() // --> 110@110
el1.position(200,200, { deep: true });
el2.position() // --> 210@210
const restrictedArea = paper.getArea();
el.position(x, y, { restrictedArea }); // --> makes sure x and y is within the area provided

removePort()

element.removePort(port, [opt])

Remove a port from an element, where port is a port object, or portId.

removePorts()

element.removePorts(ports [, opt])

Remove an array of ports from the element.

The ports can be specified as Port interfaces or portIds. The function skips over any ports in the array that do not exist on the element.

element.removePorts([opt])

If no array is provided, the function removes all ports from the element.

resize()

element.resize(
width: number,
height: number,
opt?: Element.ResizeOptions
): this;

Change the size of the element.

By default the element is resized in the direction from the top-left corner to the bottom-right corner. This means that the top-left corner of the element stays at the same position after resizing.

To change the direction of resizing, set opt.direction to one of 'left', 'right', 'top', 'bottom', 'top-right', 'top-left', 'bottom-left' or 'bottom-right' (the default).

rotate()

element.rotate(deg, [absolute, origin, opt])

Rotate an element by deg degrees around its center. If the optional absolute parameter is true, the deg will be considered an absolute angle, not an addition to the previous angle. If origin is passed in the form of an object with x and y properties, then this point will be used as the origin for the rotation transformation.

scale()

element.scale(sx, sy, origin[, opt])

Scales the element's position and size relative to the given origin.

size()

element.size(): Size;

Return the current size of the element as an object with width and height.

element.size(
size: Partial<Size>,
opt?: Element.ResizeOptions
): this;

Change the size of the element with an object that specifies the new width and height. If a value is not provided for a property, the old value is kept.

You may specify resize options in the second parameter. See the resize() method for more information.

element.size(
width: number,
height: number,
opt?: Element.ResizeOptions
): this;

Change the size of the element to the provided width and height.

You may specify resize options in the third parameter. See the resize() method for more information.

translate()

element.translate(tx, [ty], [opt])

Translate an element by tx pixels in x axis and ty pixels in y axis. ty is optional in which case the translation in y axis will be considered zero. The optional options object opt can be used to pass additional parameters to the event handlers listening on 'change:position' events. opt.transition can be used to initiate an animated transition rather than a sudden move of the element. See joint.dia.Element:transition for more info on transitions. If opt.restrictedArea is set, the translation of the element will be restricted to that area only. The restrictedArea is an object of the form { x: Number, y: Number, width: Number, height: Number }. This is useful, e.g. if you want to restrict the translation of an embedded element within its parent. The only thing you have to do in this case is to pass the bounding box of the parent element to the restrictedArea option:

myElement.translate(50, 50, { restrictedArea: graph.getCell(myElement.get('parent')).getBBox() })

The code above makes sure that the element myElement never crosses the bounding box of its parent element. note that this also works if the element myElement has other embedded elements. In other words, the bounding box of the myElement that is used to calculate the restriction is the total bounding box, including all its children (in case they are sticking out).

Events

The following list contains events that you can react on:

change

changeGeneric event triggered for any change on the element - fn(element, opt)
change:angleTriggered when the element gets rotated - fn(element, newAngle, opt)
change:attrsTriggered when the element changes its attributes - fn(element, newAttrs, opt)
change:embedsTriggered when other cells were embedded into the element - fn(element, newEmbeds, opt)
change:parentTriggered when the element got embedded into another element - fn(element, newParent, opt)
change:positionTriggered when the element changes its position - fn(element, newPosition, opt)
change:sizeTriggered when the element gets resized - fn(element, newSize, opt)
change:z

Triggered when the element is moved in the z-level (toFront and toBack) - fn(element, newZ, opt)

// Listening for changes of the position to a single element
element1.on('change:position', function(element1, position) {
alert('element1 moved to ' + position.x + ',' + position.y);
});
// All elements events are also propagated to the graph.
graph.on('change:position', function(element, position) {
console.log('Element ' + element.id + 'moved to ' + position.x + ',' + position.y);
});
// Using the option parameter and a custom attribute
graph.on('change:custom', function(element, custom, opt) {
if (opt.consoleOutput) {
console.log('Custom attribute value changed to "' + custom + '"');
}
});
element2.prop('custom', 'myValue', { consoleOutput: true });

transition

transition:startTriggered when a transition starts. - fn(element, pathToAttribute)
transition:endTriggered when a transiton ends. - fn(element, pathToAttribute)

Ports

Many diagramming applications deal with elements with ports. Ports are usually displayed as circles inside diagram elements and are used not only 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 the element as a whole, but to a certain port instead.

It's easy to add ports to arbitrary shapes in JointJS. This can be done either by passing a ports definition as an option in the constructor, or using the ports API to get/add/remove single or multiple ports. For more information on how to define ports please see the Port configuration section.

Port API on joint.dia.Element

Port configuration

// Single port definition
const port = {
// id: 'abc', // Generated if `id` value is not present
group: 'a',
args: {}, // Extra arguments for the port layout function, see `layout.Port` section
label: {
position: {
name: 'left',
args: { y: 6 } // Extra arguments for the label layout function, see `layout.PortLabel` section
},
markup: [{
tagName: 'text',
selector: 'label'
}]
},
attrs: {
body: { magnet: true, width: 16, height: 16, x: -8, y: -4, stroke: 'red', fill: 'gray'},
label: { text: 'port', fill: 'blue' }
},
markup: [{
tagName: 'rect',
selector: 'body'
}]
};

// a.) Add ports in constructor.
const rect = new joint.shapes.standard.Rectangle({
position: { x: 50, y: 50 },
size: { width: 90, height: 90 },
ports: {
groups: {
'a': {}
},
items: [
{ group: 'a' },
port
]
}
});

// b.) Or add a single port using API
rect.addPort(port);

rect.getGroupPorts('a');
/*
[
{ * Default port settings * },
{ * Follows port definition * },
{ * Follows port definition * }
]
*/
idstring

It is automatically generated if no id is provided. IDs must be unique in the context of a single shape - two ports with the same port id are therefore not allowed (Element: found id duplicities in ports. error is thrown).

groupstring

Group name, more info in the groups section.

argsobject

Arguments for the port layout function. Available properties depend on the type of layout. More information can be found in layout.Port.

attrsobject

JointJS style attribute definition. The same notation as the attrs property on Cell.

markupMarkupJSON | string

A custom port markup.

The default port markup is <circle class="joint-port-body" r="10" fill="#FFFFFF" stroke="#000000"/>.

The root of the port markup is referenced by the portRoot selector.

If the markup contains more than one node, an extra group is created to wrap the nodes. This group becomes the new portRoot.

// An example of port markup
markup: [{
tagName: 'rect',
selector: 'bodyInner',
className: 'outer',
attributes: {
'width': 15,
'height': 15,
'fill': 'red'
}
}, {
tagName: 'rect',
selector: 'bodyOuter',
className: 'inner',
attributes: {
'width': 15,
'height': 15,
'fill': 'blue',
'x': 10
}
}]
labelobject

Port label layout configuration. E.g. label position, label markup. More information about port label layouts can be found in layout.PortLabel.

  • label.position
string | object

Port label position configuration. It can be a string for setting the port layout type directly with default settings, or an object where it's possible to set the layout type and options.

{ position: 'left'}

// or ...

{
position: {
name: 'left',
args: {
dx: 10
}
}
}
  • label.position.name
string

It states the layout type. It matches the layout method name defined in the joint.layout.PortLabel namespace: name: 'left' is implemented as joint.layout.PortLabel.left.

  • label.position.args
object

Additional arguments for the layout method. Available properties depend on the layout type. More information can be found in the layout.PortLabel section.

  • label.markup
MarkupJSON | string

A custom port label markup.

The default port label markup is <text class="joint-port-label" fill="#000000"/>.

The root of the label markup is referenced by the labelRoot selector.

If the markup contains more than one node, an extra group is created to wrap the nodes. This group becomes the new labelRoot.

All <text/> nodes of the port are referenced by the labelText selector, unless the markup contains labelText explicitly.

Use an empty array [] to prevent the label from being rendered.

znumber | string

An alternative to HTML z-index. z sets the position of a port in the list of DOM elements within an ElementView.

Shapes most likely consist of 1 or more DOM elements, <rect/>, <rect/><text/><circle/> etc. Ports are placed into the main group element elementView.el, so it will act as the port container. Ports with z: 'auto' are located right after the last element in the main group. Ports with z defined as a number are placed before a DOM element at the position (index within the children of the container, where only the original markup elements, and ports with z: 'auto' are taken into account) equal to z.

For instance, the first shape from the demo above with the following markup...

markup: [{
tagName: 'rect',
selector: 'bodyMain',
className: 'bodyMain'
}, {
tagName: 'rect',
selector: 'bodyInner',
className: 'bodyInner'
}, {
tagName: 'text',
selector: 'label',
className: 'label'
}]

...will be rendered like this:

<g model-id="...">
<g class="joint-port"></g> <!-- z: 0 -->
<rect class="bodyMain"></rect>
<g class="joint-port"></g> <!-- z: 1 -->
<rect class="bodyInner"></rect>
<text class="label"></text>
<g class="joint-port"></g> <!-- z: 3 -->
<g class="joint-port"></g> <!-- z: auto -->
</g>

Ports will be placed in the rotatable group if it's defined in the shape's markup. Ports with z: 'auto' are located right after the last element in the rotatable group. In the demo above, the second shape is defined with a rotatable group and the following markup:

markup: [{
tagName: 'g',
selector: 'rotatable',
className: 'rotatable',
children: [{
tagName: 'g',
selector: 'scalable',
className: 'scalable',
children: [{
tagName: 'rect',
selector: 'bodyMain',
className: 'bodyMain'
}]
}, {
tagName: 'rect',
selector: 'bodyInner',
className: 'bodyInner'
}, {
tagName: 'text',
selector: 'label',
className: 'label'
}]
}]

It will be rendered like this:

<g model-id="...">
<g class="rotatable">
<g class="joint-port"></g> <!-- z: 0 -->
<g class="scalable"><rect class="bodyMain"></rect></g>
<g class="joint-port"></g> <!-- z: 1 -->
<rect class="bodyInner"></rect>
<text class="label"></text>
<g class="joint-port"></g> <!-- z: 3 -->
<g class="joint-port"></g> <!-- z: auto -->
</g>
</g>

All properties described above are optional, and everything has its own default. E.g. element.addPorts([{}, {}]) will add 2 ports with default settings.

Port groups configuration

While single port definitions are useful, what if we want more control over our ports? This is where a port group can come into play. A group allows us to define multiple ports with similar properties, and influence the default port alignment. Any individual port can override a property in a port group definition except the type of layout(E.g. position: 'left'). The group definition defines the layout, and the individual port args are the only way a port can affect it.

// Port definition for input ports group
const portsIn = {
position: {
name: 'left', // Layout name
args: {}, // Arguments for port layout function, properties depend on type of layout
},
label: {
position: {
name: 'left',
args: { y: 6 }
},
markup: [{
tagName: 'text',
selector: 'label',
}]
},
attrs: {
body: { magnet: 'passive', width: 15, height: 15, stroke: 'red', x: -8, y: -8 },
label: { text: 'in1', fill: 'black' }
},
markup: [{
tagName: 'rect',
selector: 'body'
}]
};

// Define port groups in element constructor
const rect = new joint.shapes.basic.Rect({
// ...
ports: {
groups: {
'group1': portsIn,
// 'group2': ...,
// 'group3': ...,
},
items: [
// Initialize 'rect' with port in group 'group1'
{
group: 'group1',
args: { y: 40 } // Overrides `args` from the group level definition for first port
}
]
}
});

// Add another port using Port API
rect.addPort(
{ group: 'group1', attrs: { label: { text: 'in2' }}}
);
positionstring | object

Port position configuration. It can be a string to set the port layout type directly with default settings, or an object where it's possible to set the layout type and options.

  • position.name
string

It states the layout type. Match the layout method name defined in the joint.layout.Port namespace: name: 'left' is implemented as joint.layout.Port.left.

  • position.args
object

Arguments for the port layout function. Available properties depend on the type of layout. More information can be found in layout.Port.

attrsobject

JointJS style attribute definition. The same notation as the attrs property on Cell.

markupMarkupJSON | string

Custom port markup. Multiple roots are not allowed. Valid notation would be:

markup: [{
tagName: 'g',
children: [{
tagName: 'rect',
selector: 'bodyInner',
className: 'outer',
attributes: {
'width': 15,
'height': 15,
'fill': 'red'
}
}, {
tagName: 'rect',
selector: 'bodyOuter',
className: 'inner',
attributes: {
'width': 15,
'height': 15,
'fill': 'blue',
'x': 10
}
}]
}]

The default port looks like the following: <circle class="joint-port-body" r="10" fill="#FFFFFF" stroke="#000000"/>.

labelobject

Port label layout configuration. E.g. label position, label markup. More information about port label layouts can be found in the layout.PortLabel section.

  • label.position
string | object

Port label position configuration. It can be a string for setting the port layout type directly with default settings, or an object where it's possible to set the layout type and options.

{ position: 'left'}

// or ...

{
position: {
name: 'left',
args: {
dx: 10
}
}
}
  • label.position.name
string

It states the layout type. It matches the layout method name defined in the joint.layout.PortLabel namespace: name: 'left' is implemented as joint.layout.PortLabel.left.

  • label.position.args
object

Additional arguments for the layout method. Available properties depend on the layout type. More information can be found in the layout.PortLabel section.

  • label.markup
MarkupJSON | string

Custom port label markup. Multiple roots are not allowed. The default port label looks like the following: <text class="joint-port-label" fill="#000000"/>.

Custom markup

Both port and port label can have custom markup...

var rect = new joint.shapes.basic.Rect({
// ...
});

rect.addPort({
markup: [{
tagName: 'rect', selector: 'body', attributes: { 'width': 20, 'height': 20, 'fill': 'blue' }
}]
});

rect.addPort({
markup: [{
tagName: 'rect', selector: 'body', attributes: { 'width': 16, 'height': 16, 'fill': 'red' }
}],
label: {
markup: [{ tagName: 'text', selector: 'label', attributes: { 'fill': '#000000' }}]
},
attrs: { label: { text: 'port' }}
});

...or, it can be set as an default port markup/port label markup on an element model:

var rect = new joint.shapes.basic.Rect({
portMarkup: [{
tagName: 'rect',
selector: 'body',
attributes: {
'width': 20,
'height': 20,
'fill': 'blue',
'stroke': 'black',
'stroke-width': 5 // Use native SVG kebab-case for attributes in markup
}
}],
portLabelMarkup: [{
tagName: 'text',
selector: 'label',
attributes: {
'fill': 'yellow'
}
}]
// ...
});