Skip to main content
Version: 4.1

BPMN shapes

JointJS+ provides you with a set of Business Process Model and Notation 2.0 shapes. They are designed to be easily configurable with the Inspector.

The plugin consists of flow shapes (Activities, Events, Gateways), connection objects (Flows), several icons and artifacts (DataObjects, Groups, Annotation). It also covers models introduced in version 2.0: Conversations and Connections.

The BPMN shapes are contained inside shapes.bpmn2 namespace and you can use them as we described earlier.

Basic shapes​

Here is an example of basic BPMN shapes:

Markers handling​

Multiple shapes in BPMN namespace contain different markers. The marker value is stored as a data attribute on the marker icon (SVG path) element. An interaction with the marker icon can be caught and handled on the dia.Paper. In the example below the model's marker name is displayed on interaction with the icon.

paper.on('element:marker:pointerdown', (cellView, evt) => {
const markerName = evt.target.dataset.iconType;

if (markerName) {
alert(markerName);
}
});

Pools​

JointJS+ also provides composite pool shapes to use in your applications: CompositePool as the outer rectangle, which may contain several Swimlanes and Phases. These map straightforwardly to BPMN pools, lanes and milestones.

JointJS+ composite pool shapes come in two distinct orientations:

Swimlanes and phases of composite pools can be added / removed using built-in methods:

Each individual composite pool shape can be be independently styled, just like any other JointJS element.

Here is an example of basic composite pool shapes (HorizontalPool compatible shapes on the left, VerticalPool compatible shapes on the right):

Here is an example of complex composite pool shapes:

BPMNFreeTransform​

The BPMNFreeTransform UI component renders a special FreeTransform control panel above a CompositePool / Swimlane / Phase / basic BPMN shape. It respects CompositePool's model attributes and keeps all components within a CompositePool in sync while any one of them is resizing.

Example​

paper.on('element:pointerup', cellView => {
new ui.BPMNFreeTransform({ cellView });
});

You can see BPMNFreeTransform in action in the Pools examples above.

Legacy Pools​

warning

Deprecated. Use the new composite pool functionality.

JointJS+ also provides complex Pool shapes to use in your applications.

The BPMN Pool consists of lanes and milestones. Let's describe how they work in terms of JointJS shapes.

Each lane and milestone group has a unique id that can be used to target those groups or individual elements within groups. Passing string to id property of a lane or milestone will create an alias to that given group.

note

Custom id must be unique and the same id cannot be assigned for more than 1 milestone or 1 lane group.

Lane group id starts with a 'lanes' prefix and a combination of numbers that is based on position within an array and nest level (sublanes). Each lane group contains the following elements:

  • lane rectangle (prefix lane)
  • header rectangle (prefix header, note that header won't be added if label is undefined)
  • label text (prefix label, note that label won't be added if it is undefined)

For example let's consider this lanes structure:

lanes: [
{
label: 'lane' // group id: 'lanes_0', lane id: 'lane_0', header id: 'header_0', label id: 'label_0'
},
{
label: 'lane with sublanes', // group id: 'lanes_1', lane id: 'lane_1', header id: 'header_1', label id: 'label_1'
sublanes: [
{
label: 'sublane 1' // group id: 'lanes_1_0', lane id: 'lane_1_0', header id: 'header_1_0', label id: 'label_1_0'
},
{
label: 'sublane 2' // group id: 'lanes_1_1', lane id: 'lane_1_1', header id: 'header_1_1', label id: 'label_1_1'
}
]
},
// example with custom id
{
id: 'customId',
label: 'lane with custom id', // group id: 'lanes_customId', lane id: 'lane_customId', header id: 'header_customId', label id: 'label_customId'
sublanes: [
{
label: 'sublane 1' // group id: 'lanes_2_0', lane id: 'lane_2_0', header id: 'header_2_0', label id: 'label_2_0'
},
{
id: 'sublaneId',
label: 'sublane 2' // group id: 'lanes_sublaneId', lane id: 'lane_sublaneId', header id: 'header_sublaneId', label id: 'label_sublaneId'
}
]
}
]

Similarly, each milestone group id starts with a 'milestone' prefix. Milestone group contains the following elements:

  • header rectangle (prefix milestoneHeader)
  • label text (prefix milestoneLabel)
  • milestone line (prefix milestoneLine)

Example milestones structure:

milestones: [
{
label: 'milestone 1' // group id: 'milestone_0', header id: 'milestoneHeader_0', label id: 'milestoneLabel_0', line id: 'milestoneLine_0'
},
{
label: 'milestone 2' // group id: 'milestone_1', header id: 'milestoneHeader_1', label id: 'milestoneLabel_1', line id: 'milestoneLine_1'
},
// example with custom id
{
id: 'customId',
label: 'milestone 3' // group id: 'milestone_customId', header id: 'milestoneHeader_customId', label id: 'milestoneLabel_customId', line id: 'milestoneLine_customId'
}
]

Here is an example of Pool shapes:

SwimlaneBoundary element tool​

The SwimlaneBoundary element tool renders a rectangular border to show the bounding box of a bpmn2.Pool and bpmn2.HeaderedPool pool lane.

Example​

const boundaryTool = new elementTools.SwimlaneBoundary({
laneId: 'customLaneId',
padding: 20
});

SwimlaneTransform element tool​

The SwimlaneTransform element tool allows you to resize lanes in bpmn2.Pool or bpmn2.HeaderedPool shape, from the UI. It renders 4 handles around selected lane which you drag and use to resize lane in particular direction.

Examples​

const transformTool = new SwimlaneTransform({
laneId: 'customLaneId',
padding: 8,
minSize: 50,
focusOpacity: 0.5
constraintsPadding: 20,
});

Example with custom constraint points:

graph.on('change:parent', function(element, parentId) {
if (parentId) {
const pool = graph.getCell(parentId);
const [laneId] = pool.getLanesFromPoint(element.getBBox().center());
element.prop('laneId', laneId);
} else {
element.prop('laneId', null);
}
});

const transformTool = new SwimlaneTransform({
laneId: 'customLaneId',
constraintsPadding: 30,
minSizeConstraints: (model, laneId, handleSide) => {
const minPoints = [];
const embedCells = model.getEmbeddedCells();

if (embedCells.length === 0) return;

embedCells.forEach(cell => {
if (cell.prop('laneId') === laneId) {
const cellBBox = cell.getBBox();
minPoints.push(cellBBox.topLeft());
minPoints.push(cellBBox.topRight());
minPoints.push(cellBBox.bottomLeft());
minPoints.push(cellBBox.bottomRight());
}
});

if (handleSide === 'left' || handleSide === 'right') {
const embedsBBox = model.graph.getCellsBBox(embedCells);
// add left most and right most points
minPoints.push(embedsBBox.origin());
minPoints.push(embedsBBox.corner());
}

return minPoints;
},
maxSizeConstraints: (model, laneId) => {
const maxPoints = [];
const embedCells = model.getEmbeddedCells();

if (embedCells.length === 0) return;

embedCells.forEach(cell => {
if (cell.prop('laneId') !== laneId) {
const cellBBox = cell.getBBox();
maxPoints.push(cellBBox.topLeft());
maxPoints.push(cellBBox.topRight());
maxPoints.push(cellBBox.bottomLeft());
maxPoints.push(cellBBox.bottomRight());
}
});

return maxPoints;
}
});