Skip to main content

Cells

Cells are the primary building blocks for any JointJS diagram. In our documentation, cells are also known as shapes, and we use these terms interchangeably. These terms combine all elements and links that you can create and add to your diagram.

All shapes are represented by their model and view. The base classes for model and view are dia.Cell and dia.CellView respectively.

dia.Cell​

All shapes are descendants of the dia.Cell class. It's an mvc.Model descendant with a few additional properties and methods. Most importantly, every cell has a unique ID that is stored in the id property.

JointJS shapes have several model attributes which are used to specify rendering of the shape and other properties. The description of such attributes is presented below. To set the default value of model attributes, as every descendant of mvc.Model, dia.Cell classes have a special defaults property, which specifies default model attributes values.

Shapes also have a special markup property, which specifies which SVG elements to render for every type of shape. Markup is one of the most important parts of a shape declaration. We discuss it in detail in a section below. Additionally, JointJS presents a set of special SVG presentation attributes that you can use in your markup declaration.

dia.CellView​

The view for the dia.Cell model. It inherits from mvc.View and is responsible for:

  • rendering a shape inside of a paper,
  • handling the shape's pointer events,
  • providing various methods for working with the shape (visually).

To find the view associated with a specific shape (model), use the findViewByModel() method of the paper. For example:

const cellView = paper.findViewByModel(cell);

Markup​

All shapes in JointJS have markup associated with it. Normally, you will specify the markup of the shape at the time of its declaration. You can find information on creating custom shapes in the corresponding section in our documentation.

Markup is either an XML string, or JSON markup specifying an array of JSON elements. Also, JSON markup can be described using an svg tagged template. The markup property is used as a template to build DOM Elements on the fly when the associated cellView is rendered.

warning

When using shapes as classes in JavaScript and TypeScript, to get data for rendering from the markup property, the shape instance needs to have the markup property set before the constructor is invoked. In that case, to set markup for a particular class, you should use the preinitialize() method.

JSON Markup​

JSON markup is defined recursively as an array of JSONElement or strings representing text nodes, where JSONElement is a plain object with the following properties:

  • tagName (string) (required) - the type of element to be created.
  • selector (string) - a unique selector for targeting the element within the attrs cell attribute.
  • groupSelector (string | string[]) - a selector for targeting multiple elements within the attrs cell attribute. The group selector name must not be the same as an existing selector name.
  • namespaceURI (string) - the namespace URI of the element. It defaults to the SVG namespace ("http://www.w3.org/2000/svg").
  • attributes (object with attributes name-value pairs) - presentation attributes of the element.
  • style (object with CSS property-value pairs) - the style attribute of the element.
  • className (string) - the class attribute of the element.
  • children (JSONMarkup) - the children of the element.
  • textContent (string) - the text content of the element.
import { dia } from '@joint/core';

// single DOM element
const markup: dia.MarkupJSON = [{ tagName: 'rect' }];

// multiple DOM elements
const markup: dia.MarkupJSON = [{
tagName: 'rect',
selector: 'body'
}, {
tagName: 'text',
selector: 'label',
attributes: {
'stroke': 'none'
}
}]

// nested DOM elements
const markup: dia.MarkupJSON = [{
tagName: 'g',
children: [{
tagName: 'circle',
selector: 'circle1',
groupSelector: 'circles'
},
'text content',
{
tagName: 'circle',
selector: 'circle2',
groupSelector: 'circles'
}]
}]

JSON Markup using tagged template​

JSON markup can also be defined using an svg tagged template. This tagged template converts an SVG representation of the markup into a JSON markup object. Let's write the previous example using an svg tagged template:

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

// single DOM element
const markup: dia.MarkupJSON = util.svg`<rect \>`

// multiple DOM elements
const markup: dia.MarkupJSON = util.svg`
<rect @selector="body" \>
<text
@selector="label"
stroke="none"
\>`

// nested DOM elements
const markup: dia.MarkupJSON = util.svg`
<g>
<circle
@selector="circle1"
@group-selector="circles"
/>
text content
<circle
@selector="circle2"
@group-selector="circles"
/>
</g>`

JSON Markup presentation attributes​

Anything you define in markup is evaluated once at CellView creation (the DOM elements and their attributes). That means it's important to think about the runtime of your application. If you have SVG attributes that don't change throughout the runtime, you can add them to the markup.

As markup is something all instances of the shape type are expected to have in common, inheriting from the subtype prototype is more efficient. Nevertheless, it is still possible to provide custom markup to individual instances of your class by providing markup later.

Anything in the attrs attribute is evaluated on every change of the model (e.g. a resize or an attrs change). As JointJS special presentation attributes mostly depend on the current state of the model (size, attrs, angle), they should always be defined inside attrs.

SVG attributes that are modified at some point in the application should also be added to attrs (e.g. user changes the color of the element).

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

const standardRectangle = dia.Element.define('standard.Rectangle', {
attrs: {
body: {
width: 'calc(w)', // special calc attribute expression
height: 'calc(h)',
stroke: 'red',
fill: '#333333' // We wan't to modify the fill color of our instances
},
label: {
// attributes
}
}
}, {
markup: [{
tagName: 'rect',
selector: 'body',
attributes: {
strokeWidth: 2
}
}, {
tagName: 'text',
selector: 'label',
attributes: {
stroke: 'none' // We don't need any instances to have a stroke value for text
}
}]
});

Setting presentation attributes with selectors​

Here are simple rules how SVG attributes are set on the nodes when one uses a combination of selector and groupSelector.

  • selector is unique i.e. can target a single node only.
  • selector takes precedence over groupSelector.
  • groupSelector targeting n nodes takes precedence over groupSelector targeting m nodes for n < m. When n === m the order how the presentation attributes are applied is unspecified.

In the example below, the circle with the selector circle1 is filled with 'red' color. All the other circles are filled with 'blue' color. You can find more information on selector interactions in the presentation attributes section below.

cell.attr({
circle1: { fill: 'red' },
circles: { fill: 'blue', stroke: 'black' }
});

Special selectors​

There are a few special selectors that can be used in the markup property:

scalable​

The scalable <g/> of the shape. It scales its content to fit the size of the model.

markup: util.svg`
<g @selector="scalable">
<rect x="0" width="10" height="10" fill="red"/>
<rect x="10" width="10" height="10" fill="blue"/>
</g>
`
important

It should be avoided unless using calc() expressions is not an option.

When using the scalable selector, the shape needs to be rendered, measured and then transformed. This can have an impact on the performance for large diagrams.

The example above can be rewritten using calc() expressions:

markup: util.svg`
<rect @selector="r1" fill="red" />
<rect @selector="r2" fill="blue" />
`,
attrs: {
r1: {
x: 0,
width: 'calc(w/2)',
height: 'calc(h)'
},
r2: {
x: 'calc(w/2)',
width: 'calc(w/2)',
height: 'calc(h)'
}
}
note

The width and height of the shape are set in the size attribute of the model.

el.set('size', { width: 100, height: 100 });

rotatable​

The rotatable <g/> of the shape. By default, the whole markup is rotated around the center of the shape. If you want to rotate only a part of the markup, you can use the rotatable selector.

markup: util.svg`
<g @selector="rotatable">
<rect /><!-- this rect will be rotated -->
</g>
<text /><!-- this text will not be rotated -->
`
important

The rotatable selector is useful when you want to rotate only a part of the shape. If you want to rotate the whole shape, the rotatable group is implicitly the root group of the shape.

note

The angle of rotation in degrees is set in the angle attribute of the model.

el.set('angle', 45);

Implicit selectors​

There are also selectors that are assigned implicitly to important nodes of the shape:

root​

The wrapping <g/> of the shape in the context of a shape.

<g class="joint-element"> <!-- root -->
<rect />
<text />
</g>

The wrapping <g/> of the port in the context of a port.

<g class="joint-port"> <!-- root -->
<circle class="joint-port-body" />
<text class="joint-port-label" />
</g>

The wrapping <g/> of the label in the context of a link label.

<g class="label"> <!-- root -->
<rect />
<text />
</g>

portRoot​

The root node of the port's body (excluding the label) in the context of a port.

It is either the implicit wrapping group <g/> in case the port consist of multiple SVGElements

<g class="joint-port">
<g class="joint-port-body"> <!-- portRoot -->
<rect />
<rect />
</g>
<text class="joint-port" />
</g>

or a single SVGElement of the port.

<g class="joint-port">
<circle class="joint-port-body" /> <!-- portRoot -->
<text class="joint-port-label" />
</g>

labelRoot​

The root node of the label in the context of a port.

Either the implicit wrapping group <g/> in case the label consist of multiple SVGElements

<g class="joint-port">
<circle class="joint-port-body"/>
<g class="joint-port-label" /> <!-- labelRoot -->
<rect />
<text />
</g>
</>

or a single SVGElement of the label.

<g class="joint-port">
<circle class="joint-port-body"/>
<text class="joint-port-label" /> <!-- labelRoot -->
</g>

labelText​

The <text /> of the label in the context of a port.

<g class="joint-port">
<circle class="joint-port-body" />
<g class="joint-port-label" />
<rect />
<text /> <!-- labelText -->
</g>
</>

Markup without selectors​

Markup can also be specified as a valid XML string that contains either a single tagName or XML that can be parsed with DOMParser.

markup: 'rect'
markup: '<rect class="rectangle"/>'
markup: '<rect><g><circle/><circle/></g>'
important

Defining Cell markup with XML strings is slower than defining it with JSON arrays, precisely because of the need for parsing. We strongly recommend you to use the JSON markup for your Cell definitions.

Note that you also need to set the useCSSSelectors=true on the model's prototype in order to use CSS selectors in the attrs attribute.

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

const rectangle = dia.Element.define('Rectangle', {
attrs: {
rect: { // CSS Selector for the <rect/> element
fill: 'red'
}
}
}, {
markup: '<rect class="rectangle"/>',
useCSSSelectors: true
});

Model attributes​

Shapes contain several model attributes, common for both elements and links. These attributes work using mvc.Model attributes functionality. Elements and links provide additional sets of model attributes, which are specific to their respective purposes. You can find description of these attributes in the Element model attributes and Link model attributes sections of our documentation.

Working with model attributes​

To work with the model attributes we recommend using prop() and set() methods. These methods will help you store any data on the model, while at the same time also providing a nice separation between model and presentation attributes.

note

You can store any custom data inside the shape using model attributes. However, it is recommended to use the data attribute for this purpose. This attribute is reserved for custom data and is not used by JointJS for any other purpose.

The prop() method​

The prop() method is used to set attributes on the shape model. It can be used to set both model and presentation attributes, and it also provides support for nesting making it very flexible. When setting an attribute, the first parameter is an object or string representation of our path, and when not using an object, the second parameter will be the value we wish to set. Prop will merge the properties you want to set with existing ones already present in the shape model.

element.prop('size/width', 100); // Set element attribute

element.prop('attrs/body/stroke', '#FFF'); // Set presentation attribute

element.prop('data', 10); // Set custom attribute with string
element.prop({ data: 10 }); // Set custom attribute with object

// Output from element.toJSON();
{
"type": "standard.Rectangle",
"position": { "x": 0, "y": 0 },
"size": { "width": 100, "height": 1 }, // Element attribute
"angle": 0,
"id": "19cf14e6-cb78-4c32-b9a1-4256dc53776f",
"data": 10, // Our custom attribute
"attrs": {
"body": {
"stroke": "#FFF" // Our presentation attribute
}
}
}


element.prop('data/count', 10); // Set nested custom attribute with string
element.prop({ data: { count: 10 }}); // Set nested custom attribute with object
element.prop('data': { count: 10 }); // This also creates a nested custom attribute

{
"type": "standard.Rectangle",
"position": { "x": 0, "y": 0 },
"size": { "width": 1, "height": 1 },
"angle": 0,
"id": "19cf14e6-cb78-4c32-b9a1-4256dc53776f",
"data": {
"count": 10 // Our nested custom attribute
},
"attrs": {}
}

prop() not only provides support for nested objects, but for nested arrays too.

element.prop('mylist/0/data/0/value', 50); // Set custom attribute as nested array

// Output from element.toJSON();
{
"type": "standard.Rectangle",
"position": { "x": 0, "y": 0 },
"size": { "width": 1, "height": 1 },
"angle": 0,
"id": "9adab5e5-cebe-419f-8d62-39cce5486d0d",
"mylist": [
{
"data": [
{
"value": 50 // Our nested custom attribute
}
]
}
],
"attrs": {}
}
note

You can pass { isolate: true } if the property change does not affect the connected links. Typically, changing the color has zero effect on attached links. By default, the cell and all connected links are updated.

cell.attr(['line', 'stroke'], 'red', { isolate: true });

The set() method​

The set() is a method provided by mvc.Model, and similarly to prop(), it can be used to set any attributes on the shape model. Like prop(), when setting an attribute, the first parameter can be an object or string, but set() doesn't provide nesting capability in the form of a string. That means any path representation is considered to be one attribute. Again, when not using an object, the second parameter is the value we wish to set. Another difference to take note of is that set() will override attributes, while prop() merges them.

element.set('size', { width: 100, height: 50 }); // Set element attribute
element.set('data', 10); // Set custom attribute with string
element.set({ data: 10 }); // Set custom attribute with object

// Output from element.toJSON();
{
"type": "standard.Rectangle",
"position": { "x": 0, "y": 0 },
"size": { "width": 100, "height": 50 },
"angle": 0,
"id": "c0a6696d-1857-4fb7-892d-409433f84d29",
"data": 10, // Our custom attribute
"attrs": {}
}

element.set('data/count', 10); // We try to set a nested custom property using set()

// The output produced will not be nested as is the case when using prop()
{
"type": "standard.Rectangle",
"position": { "x": 0, "y": 0 },
"size": { "width": 1, "height": 1 },
"angle": 0,
"id": "afdc42ce-5b10-45e5-832e-afcf2a221314",
"data/count": 10, // Note the important difference here
"attrs": {}
}

Overwriting attributes with prop()​

We do provide some extra functionality when using prop, and that is to enable rewrite mode. To enable rewrite mode, we simply use { rewrite: true } as the 3rd argument in our prop() method. This will replace the value referenced by the path with the new one. This differs from the default behavior which is to merge our properties.

element.prop('custom/state/isCollapsed', true);
element.prop('custom/state', { isActive: false });

// Output from element.toJSON();

// We can see our attributes have been merged
{
"type": "standard.Rectangle",
"position": { "x": 0, "y": 0 },
"size": { "width": 1, "height": 1 },
"angle": 0,
"id": "b1c02090-e46a-4d90-a5dc-5096f1559b9f",
"custom": {
"state": {
"isCollapsed": true,
"isActive": false
}
},
"attrs": {}
}


element.prop('custom/state/isCollapsed', true);
element.prop('custom/state', { isActive: false }, { rewrite: true });

// We can see our attributes have been overwritten
{
"type": "standard.Rectangle",
"position": { "x": 0, "y": 0 },
"size": { "width": 1, "height": 1 },
"angle": 0,
"id": "b1c02090-e46a-4d90-a5dc-5096f1559b9f",
"custom": {
"state": {
"isActive": false
}
},
"attrs": {}
}

Relationship between prop() and attr() methods​

Both of these methods function similarly, but there are a few small differences to be aware of. Internally, attr() implements prop() to process our attributes. Afterwards, the method places the presentation attributes within the attrs object. Separating attributes in this manner also provides our model with a nice semantic and organizational divide between our model and presentation properties.

In the following example, you can see both attr() and prop() in action. It would be possible to set both of these attributes using prop(), but as mentioned above, both these methods achieve what we want in our example. We see that nice separation between attributes, because after attr() implements prop(), it also prepends our path with 'attrs'. This means we find our presentation attributes in the attrs object.

element.attr('body/strokeWidth', 2);
element.prop('isCollapsed', true);

// Output from element.toJSON();
{
"type": "standard.Rectangle",
"position": { "x": 0, "y": 0 },
"size": { "width": 1, "height": 1 },
"angle": 0,
"id": "edafa2ac-27e6-4fbc-951a-aa3f9512c741",
"isCollapsed": true,
"attrs": {
"body": {
"strokeWidth": 2
}
}
}

Another important note to mention when talking about prop() and attr() is that when changing the model, some useful information is passed along with the change event in JointJS. propertyPath, propertyValue, and propertyPathArray are all values which can be accessed when updating the model. This can prove useful if for some reason you need to listen to a specific attribute change. Note that it is not possible to access these values in this manner when using set().

graph.on('change', (cell, opt) => {
if ('attrs' in cell.changed) {
console.log(opt.propertyPathArray, 'was changed');
// --> ['attrs', 'body', 'fill'] 'was changed'
}

if ('isInteractive' in cell.changed) {
console.log(opt.propertyPathArray, 'was changed');
// --> ['isInteractive'] 'was changed'
}
});

element.attr('body/fill', 'cornflowerblue');
element.prop('isInteractive', true);

Attributes​

Here is the list of the attributes presented in all shapes.

Type​

Every shape has its own type attribute which JointJS needs to find a corresponding view to properly render shapes. Also, this type is used to get the correct class to create shapes when using serialization. More information on a shape's type can be found in the cell namespaces section of our documentation.

Presentation attributes​

The presentation attributes are stored in a special attrs attribute. The keys of the attrs object are selectors that match subelements defined in the element's markup. The values of this object are SVG attributes that will be set on the selected subelements. One can find the full list of SVG attributes and their descriptions online, e.g. on MDN.

Model and presentation attributes

In the current documentation, we are separating model attributes, which are part of our model-view architecture and presentation attributes, which are SVG attributes that can be applied to the DOM markup.

For example, in order to set a red fill color on a subelement called 'body', the attrs object would contain:

body: { fill: 'red' }

If you simply need to change a value of an SVG attribute, it is not recommended to modify the attrs object directly. Instead, use the attr() method.

element.attr('body/fill', 'red');

We can use the shapes.standard.Rectangle element (which inherits from dia.Element) as an example. The attrs object in its definition is provided below:

attrs: {
body: {
width: 'calc(w)',
height: 'calc(h)',
strokeWidth: 2,
stroke: '#000000',
fill: '#FFFFFF'
},
label: {
textVerticalAnchor: 'middle',
textAnchor: 'middle',
x: 'calc(0.5*w)',
y: 'calc(0.5*h)',
fontSize: 14,
fill: '#333333'
}
}

Notice that the object makes use of special JointJS attributes (e.g. textVerticalAnchor) on top of standard SVG attributes (e.g. stroke, fill). All of these special attributes are listed in the attributes section of this documentation. You should also refer to the section on special presentation attributes.

Z​

The z attribute specifies the stack order of the shape in the SVG DOM. A shape with a higher z value will be rendered in front of a shape with a lower z value.