UNPKG

bpmn-js

Version:

A bpmn 2.0 toolkit and web modeler

1,813 lines (1,511 loc) 64.2 kB
import inherits from 'inherits-browser'; import { assign, forEach, isObject } from 'min-dash'; import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer'; import { isExpanded, isHorizontal, isEventSubProcess } from '../util/DiUtil'; import { getLabel } from '../util/LabelUtil'; import { is } from '../util/ModelUtil'; import { createLine } from 'diagram-js/lib/util/RenderUtil'; import { isTypedEvent, isThrowEvent, isCollection, getBounds, getDi, getSemantic, getCirclePath, getRoundRectPath, getDiamondPath, getRectPath, getFillColor, getStrokeColor, getLabelColor, getHeight, getWidth } from './BpmnRenderUtil'; import { query as domQuery } from 'min-dom'; import { append as svgAppend, attr as svgAttr, create as svgCreate, classes as svgClasses } from 'tiny-svg'; import { rotate, transform, translate } from 'diagram-js/lib/util/SvgTransformUtil'; import Ids from 'ids'; import { black } from './BpmnRenderUtil'; var markerIds = new Ids(); var ELEMENT_LABEL_DISTANCE = 10, INNER_OUTER_DIST = 3, PARTICIPANT_STROKE_WIDTH = 1.5, TASK_BORDER_RADIUS = 10; var DEFAULT_OPACITY = 0.95, FULL_OPACITY = 1, LOW_OPACITY = 0.25; /** * @typedef { Partial<{ * defaultFillColor: string, * defaultStrokeColor: string, * defaultLabelColor: string * }> } BpmnRendererConfig * * @typedef { Partial<{ * fill: string, * stroke: string, * width: string, * height: string * }> } Attrs */ /** * @typedef { import('../model/Types').Element } Element * @typedef { import('../model/Types').Shape } Shape * @typedef { import('../model/Types').Connection } Connection */ /** * A renderer for BPMN elements * * @param {BpmnRendererConfig} config * @param {import('diagram-js/lib/core/EventBus').default} eventBus * @param {import('diagram-js/lib/draw/Styles').default} styles * @param {import('./PathMap').default} pathMap * @param {import('diagram-js/lib/core/Canvas').default} canvas * @param {import('./TextRenderer').default} textRenderer * @param {number} [priority] */ export default function BpmnRenderer( config, eventBus, styles, pathMap, canvas, textRenderer, priority) { BaseRenderer.call(this, eventBus, priority); var defaultFillColor = config && config.defaultFillColor, defaultStrokeColor = config && config.defaultStrokeColor, defaultLabelColor = config && config.defaultLabelColor; function shapeStyle(attrs) { return styles.computeStyle(attrs, { strokeLinecap: 'round', strokeLinejoin: 'round', stroke: black, strokeWidth: 2, fill: 'white' }); } function lineStyle(attrs) { return styles.computeStyle(attrs, [ 'no-fill' ], { strokeLinecap: 'round', strokeLinejoin: 'round', stroke: black, strokeWidth: 2 }); } function addMarker(id, options) { var { ref = { x: 0, y: 0 }, scale = 1, element, parentGfx = canvas._svg } = options; var marker = svgCreate('marker', { id: id, viewBox: '0 0 20 20', refX: ref.x, refY: ref.y, markerWidth: 20 * scale, markerHeight: 20 * scale, orient: 'auto' }); svgAppend(marker, element); var defs = domQuery(':scope > defs', parentGfx); if (!defs) { defs = svgCreate('defs'); svgAppend(parentGfx, defs); } svgAppend(defs, marker); } function marker(parentGfx, type, fill, stroke) { var id = markerIds.nextPrefixed('marker-'); createMarker(parentGfx, id, type, fill, stroke); return 'url(#' + id + ')'; } function createMarker(parentGfx, id, type, fill, stroke) { if (type === 'sequenceflow-end') { var sequenceflowEnd = svgCreate('path', { d: 'M 1 5 L 11 10 L 1 15 Z', ...shapeStyle({ fill: stroke, stroke: stroke, strokeWidth: 1 }) }); addMarker(id, { element: sequenceflowEnd, ref: { x: 11, y: 10 }, scale: 0.5, parentGfx }); } if (type === 'messageflow-start') { var messageflowStart = svgCreate('circle', { cx: 6, cy: 6, r: 3.5, ...shapeStyle({ fill, stroke: stroke, strokeWidth: 1, // fix for safari / chrome / firefox bug not correctly // resetting stroke dash array strokeDasharray: [ 10000, 1 ] }) }); addMarker(id, { element: messageflowStart, ref: { x: 6, y: 6 }, parentGfx }); } if (type === 'messageflow-end') { var messageflowEnd = svgCreate('path', { d: 'm 1 5 l 0 -3 l 7 3 l -7 3 z', ...shapeStyle({ fill, stroke: stroke, strokeWidth: 1, // fix for safari / chrome / firefox bug not correctly // resetting stroke dash array strokeDasharray: [ 10000, 1 ] }) }); addMarker(id, { element: messageflowEnd, ref: { x: 8.5, y: 5 }, parentGfx }); } if (type === 'association-start') { var associationStart = svgCreate('path', { d: 'M 11 5 L 1 10 L 11 15', ...lineStyle({ fill: 'none', stroke, strokeWidth: 1.5, // fix for safari / chrome / firefox bug not correctly // resetting stroke dash array strokeDasharray: [ 10000, 1 ] }) }); addMarker(id, { element: associationStart, ref: { x: 1, y: 10 }, scale: 0.5, parentGfx }); } if (type === 'association-end') { var associationEnd = svgCreate('path', { d: 'M 1 5 L 11 10 L 1 15', ...lineStyle({ fill: 'none', stroke, strokeWidth: 1.5, // fix for safari / chrome / firefox bug not correctly // resetting stroke dash array strokeDasharray: [ 10000, 1 ] }) }); addMarker(id, { element: associationEnd, ref: { x: 11, y: 10 }, scale: 0.5, parentGfx }); } if (type === 'conditional-flow-marker') { var conditionalFlowMarker = svgCreate('path', { d: 'M 0 10 L 8 6 L 16 10 L 8 14 Z', ...shapeStyle({ fill, stroke: stroke }) }); addMarker(id, { element: conditionalFlowMarker, ref: { x: -1, y: 10 }, scale: 0.5, parentGfx }); } if (type === 'conditional-default-flow-marker') { var defaultFlowMarker = svgCreate('path', { d: 'M 6 4 L 10 16', ...shapeStyle({ stroke: stroke, fill: 'none' }) }); addMarker(id, { element: defaultFlowMarker, ref: { x: 0, y: 10 }, scale: 0.5, parentGfx }); } } function drawCircle(parentGfx, width, height, offset, attrs = {}) { if (isObject(offset)) { attrs = offset; offset = 0; } offset = offset || 0; attrs = shapeStyle(attrs); var cx = width / 2, cy = height / 2; var circle = svgCreate('circle', { cx: cx, cy: cy, r: Math.round((width + height) / 4 - offset), ...attrs }); svgAppend(parentGfx, circle); return circle; } function drawRect(parentGfx, width, height, r, offset, attrs) { if (isObject(offset)) { attrs = offset; offset = 0; } offset = offset || 0; attrs = shapeStyle(attrs); var rect = svgCreate('rect', { x: offset, y: offset, width: width - offset * 2, height: height - offset * 2, rx: r, ry: r, ...attrs }); svgAppend(parentGfx, rect); return rect; } function drawDiamond(parentGfx, width, height, attrs) { var x_2 = width / 2; var y_2 = height / 2; var points = [ { x: x_2, y: 0 }, { x: width, y: y_2 }, { x: x_2, y: height }, { x: 0, y: y_2 } ]; var pointsString = points.map(function(point) { return point.x + ',' + point.y; }).join(' '); attrs = shapeStyle(attrs); var polygon = svgCreate('polygon', { ...attrs, points: pointsString }); svgAppend(parentGfx, polygon); return polygon; } /** * @param {SVGElement} parentGfx * @param {Point[]} waypoints * @param {any} attrs * @param {number} [radius] * * @return {SVGElement} */ function drawLine(parentGfx, waypoints, attrs, radius) { attrs = lineStyle(attrs); var line = createLine(waypoints, attrs, radius); svgAppend(parentGfx, line); return line; } /** * @param {SVGElement} parentGfx * @param {Point[]} waypoints * @param {any} attrs * * @return {SVGElement} */ function drawConnectionSegments(parentGfx, waypoints, attrs) { return drawLine(parentGfx, waypoints, attrs, 5); } function drawPath(parentGfx, d, attrs) { attrs = lineStyle(attrs); var path = svgCreate('path', { ...attrs, d }); svgAppend(parentGfx, path); return path; } function drawMarker(type, parentGfx, path, attrs) { return drawPath(parentGfx, path, assign({ 'data-marker': type }, attrs)); } function renderer(type) { return handlers[type]; } function as(type) { return function(parentGfx, element, attrs) { return renderer(type)(parentGfx, element, attrs); }; } var eventIconRenderers = { 'bpmn:MessageEventDefinition': function(parentGfx, element, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_MESSAGE', { xScaleFactor: 0.9, yScaleFactor: 0.9, containerWidth: attrs.width || element.width, containerHeight: attrs.height || element.height, position: { mx: 0.235, my: 0.315 } }); var fill = isThrowing ? getStrokeColor(element, defaultStrokeColor, attrs.stroke) : getFillColor(element, defaultFillColor, attrs.fill); var stroke = isThrowing ? getFillColor(element, defaultFillColor, attrs.fill) : getStrokeColor(element, defaultStrokeColor, attrs.stroke); var messagePath = drawPath(parentGfx, pathData, { fill, stroke, strokeWidth: 1 }); return messagePath; }, 'bpmn:TimerEventDefinition': function(parentGfx, element, attrs = {}) { var baseWidth = attrs.width || element.width; var baseHeight = attrs.height || element.height; // use a lighter stroke for event suprocess icons var strokeWidth = attrs.width ? 1 : 2; var circle = drawCircle(parentGfx, baseWidth, baseHeight, 0.2 * baseHeight, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: strokeWidth }); var pathData = pathMap.getScaledPath('EVENT_TIMER_WH', { xScaleFactor: 0.75, yScaleFactor: 0.75, containerWidth: baseWidth, containerHeight: baseHeight, position: { mx: 0.5, my: 0.5 } }); drawPath(parentGfx, pathData, { stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: strokeWidth }); for (var i = 0; i < 12; i++) { var linePathData = pathMap.getScaledPath('EVENT_TIMER_LINE', { xScaleFactor: 0.75, yScaleFactor: 0.75, containerWidth: baseWidth, containerHeight: baseHeight, position: { mx: 0.5, my: 0.5 } }); var width = baseWidth / 2, height = baseHeight / 2; drawPath(parentGfx, linePathData, { strokeWidth: 1, stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), transform: 'rotate(' + (i * 30) + ',' + height + ',' + width + ')' }); } return circle; }, 'bpmn:EscalationEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_ESCALATION', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.5, my: 0.2 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : getFillColor(event, defaultFillColor, attrs.fill); return drawPath(parentGfx, pathData, { fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:ConditionalEventDefinition': function(parentGfx, event, attrs = {}) { var pathData = pathMap.getScaledPath('EVENT_CONDITIONAL', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.5, my: 0.222 } }); return drawPath(parentGfx, pathData, { fill: getFillColor(event, defaultFillColor, attrs.fill), stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:LinkEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_LINK', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: event.width, containerHeight: event.height, position: { mx: 0.57, my: 0.263 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : getFillColor(event, defaultFillColor, attrs.fill); return drawPath(parentGfx, pathData, { fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:ErrorEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_ERROR', { xScaleFactor: 1.1, yScaleFactor: 1.1, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.2, my: 0.722 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : getFillColor(event, defaultFillColor, attrs.fill); return drawPath(parentGfx, pathData, { fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:CancelEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_CANCEL_45', { xScaleFactor: 1.0, yScaleFactor: 1.0, containerWidth: event.width, containerHeight: event.height, position: { mx: 0.638, my: -0.055 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : 'none'; var path = drawPath(parentGfx, pathData, { fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); rotate(path, 45); return path; }, 'bpmn:CompensateEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_COMPENSATION', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.22, my: 0.5 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : getFillColor(event, defaultFillColor, attrs.fill); return drawPath(parentGfx, pathData, { fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:SignalEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_SIGNAL', { xScaleFactor: 0.9, yScaleFactor: 0.9, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.5, my: 0.2 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : getFillColor(event, defaultFillColor, attrs.fill); return drawPath(parentGfx, pathData, { strokeWidth: 1, fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke) }); }, 'bpmn:MultipleEventDefinition': function(parentGfx, event, attrs = {}, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_MULTIPLE', { xScaleFactor: 1.1, yScaleFactor: 1.1, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.211, my: 0.36 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor, attrs.stroke) : getFillColor(event, defaultFillColor, attrs.fill); return drawPath(parentGfx, pathData, { fill, stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:ParallelMultipleEventDefinition': function(parentGfx, event, attrs = {}) { var pathData = pathMap.getScaledPath('EVENT_PARALLEL_MULTIPLE', { xScaleFactor: 1.2, yScaleFactor: 1.2, containerWidth: attrs.width || event.width, containerHeight: attrs.height || event.height, position: { mx: 0.458, my: 0.194 } }); return drawPath(parentGfx, pathData, { fill: getFillColor(event, defaultFillColor, attrs.fill), stroke: getStrokeColor(event, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); }, 'bpmn:TerminateEventDefinition': function(parentGfx, element, attrs = {}) { var circle = drawCircle(parentGfx, element.width, element.height, 8, { fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 4 }); return circle; } }; function renderEventIcon(element, parentGfx, attrs = {}, proxyElement) { var semantic = getSemantic(element), isThrowing = isThrowEvent(semantic); var nodeElement = proxyElement || element; if (semantic.get('eventDefinitions') && semantic.get('eventDefinitions').length > 1) { if (semantic.get('parallelMultiple')) { return eventIconRenderers[ 'bpmn:ParallelMultipleEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } else { return eventIconRenderers[ 'bpmn:MultipleEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } } if (isTypedEvent(semantic, 'bpmn:MessageEventDefinition')) { return eventIconRenderers[ 'bpmn:MessageEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:TimerEventDefinition')) { return eventIconRenderers[ 'bpmn:TimerEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:ConditionalEventDefinition')) { return eventIconRenderers[ 'bpmn:ConditionalEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:SignalEventDefinition')) { return eventIconRenderers[ 'bpmn:SignalEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:EscalationEventDefinition')) { return eventIconRenderers[ 'bpmn:EscalationEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:LinkEventDefinition')) { return eventIconRenderers[ 'bpmn:LinkEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:ErrorEventDefinition')) { return eventIconRenderers[ 'bpmn:ErrorEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:CancelEventDefinition')) { return eventIconRenderers[ 'bpmn:CancelEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:CompensateEventDefinition')) { return eventIconRenderers[ 'bpmn:CompensateEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } if (isTypedEvent(semantic, 'bpmn:TerminateEventDefinition')) { return eventIconRenderers[ 'bpmn:TerminateEventDefinition' ](parentGfx, nodeElement, attrs, isThrowing); } return null; } var taskMarkerRenderers = { 'ParticipantMultiplicityMarker': function(parentGfx, element, attrs = {}) { var width = getWidth(element, attrs), height = getHeight(element, attrs); var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: width, containerHeight: height, position: { mx: ((width / 2 - 6) / width), my: (height - 15) / height } }); drawMarker('participant-multiplicity', parentGfx, markerPath, { strokeWidth: 2, fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); }, 'SubProcessMarker': function(parentGfx, element, attrs = {}) { var markerRect = drawRect(parentGfx, 14, 14, 0, { strokeWidth: 1, fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); translate(markerRect, element.width / 2 - 7.5, element.height - 20); var markerPath = pathMap.getScaledPath('MARKER_SUB_PROCESS', { xScaleFactor: 1.5, yScaleFactor: 1.5, containerWidth: element.width, containerHeight: element.height, position: { mx: (element.width / 2 - 7.5) / element.width, my: (element.height - 20) / element.height } }); drawMarker('sub-process', parentGfx, markerPath, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); }, 'ParallelMarker': function(parentGfx, element, attrs) { var width = getWidth(element, attrs), height = getHeight(element, attrs); var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: width, containerHeight: height, position: { mx: ((width / 2 + attrs.parallel) / width), my: (height - 20) / height } }); drawMarker('parallel', parentGfx, markerPath, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); }, 'SequentialMarker': function(parentGfx, element, attrs) { var markerPath = pathMap.getScaledPath('MARKER_SEQUENTIAL', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: ((element.width / 2 + attrs.seq) / element.width), my: (element.height - 19) / element.height } }); drawMarker('sequential', parentGfx, markerPath, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); }, 'CompensationMarker': function(parentGfx, element, attrs) { var markerMath = pathMap.getScaledPath('MARKER_COMPENSATION', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: ((element.width / 2 + attrs.compensation) / element.width), my: (element.height - 13) / element.height } }); drawMarker('compensation', parentGfx, markerMath, { strokeWidth: 1, fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); }, 'LoopMarker': function(parentGfx, element, attrs) { var width = getWidth(element, attrs), height = getHeight(element, attrs); var markerPath = pathMap.getScaledPath('MARKER_LOOP', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: width, containerHeight: height, position: { mx: ((width / 2 + attrs.loop) / width), my: (height - 7) / height } }); drawMarker('loop', parentGfx, markerPath, { strokeWidth: 1.5, fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeMiterlimit: 0.5 }); }, 'AdhocMarker': function(parentGfx, element, attrs) { var width = getWidth(element, attrs), height = getHeight(element, attrs); var markerPath = pathMap.getScaledPath('MARKER_ADHOC', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: width, containerHeight: height, position: { mx: ((width / 2 + attrs.adhoc) / width), my: (height - 15) / height } }); drawMarker('adhoc', parentGfx, markerPath, { strokeWidth: 1, fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); } }; function renderTaskMarker(type, parentGfx, element, attrs) { taskMarkerRenderers[ type ](parentGfx, element, attrs); } function renderTaskMarkers(parentGfx, element, taskMarkers, attrs = {}) { attrs = { fill: attrs.fill, stroke: attrs.stroke, width: getWidth(element, attrs), height: getHeight(element, attrs) }; var semantic = getSemantic(element); var subprocess = taskMarkers && taskMarkers.includes('SubProcessMarker'); if (subprocess) { attrs = { ...attrs, seq: -21, parallel: -22, compensation: -42, loop: -18, adhoc: 10 }; } else { attrs = { ...attrs, seq: -5, parallel: -6, compensation: -27, loop: 0, adhoc: 10 }; } forEach(taskMarkers, function(marker) { renderTaskMarker(marker, parentGfx, element, attrs); }); if (semantic.get('isForCompensation')) { renderTaskMarker('CompensationMarker', parentGfx, element, attrs); } if (is(semantic, 'bpmn:AdHocSubProcess')) { renderTaskMarker('AdhocMarker', parentGfx, element, attrs); } var loopCharacteristics = semantic.get('loopCharacteristics'), isSequential = loopCharacteristics && loopCharacteristics.get('isSequential'); if (loopCharacteristics) { if (isSequential === undefined) { renderTaskMarker('LoopMarker', parentGfx, element, attrs); } if (isSequential === false) { renderTaskMarker('ParallelMarker', parentGfx, element, attrs); } if (isSequential === true) { renderTaskMarker('SequentialMarker', parentGfx, element, attrs); } } } function renderLabel(parentGfx, label, attrs = {}) { attrs = assign({ size: { width: 100 } }, attrs); var text = textRenderer.createText(label || '', attrs); svgClasses(text).add('djs-label'); svgAppend(parentGfx, text); return text; } function renderEmbeddedLabel(parentGfx, element, align, attrs = {}) { var semantic = getSemantic(element); var box = getBounds({ x: element.x, y: element.y, width: element.width, height: element.height }, attrs); return renderLabel(parentGfx, semantic.name, { align, box, padding: 7, style: { fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke) } }); } function renderExternalLabel(parentGfx, element, attrs = {}) { var box = { width: 90, height: 30, x: element.width / 2 + element.x, y: element.height / 2 + element.y }; return renderLabel(parentGfx, getLabel(element), { box: box, fitBox: true, style: assign( {}, textRenderer.getExternalStyle(), { fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke) } ) }); } function renderLaneLabel(parentGfx, text, element, attrs = {}) { var isHorizontalLane = isHorizontal(element); var textBox = renderLabel(parentGfx, text, { box: { height: 30, width: isHorizontalLane ? getHeight(element, attrs) : getWidth(element, attrs), }, align: 'center-middle', style: { fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor, attrs.stroke) } }); if (isHorizontalLane) { var top = -1 * getHeight(element, attrs); transform(textBox, 0, -top, 270); } } function renderActivity(parentGfx, element, attrs = {}) { var { width, height } = getBounds(element, attrs); return drawRect(parentGfx, width, height, TASK_BORDER_RADIUS, { ...attrs, fill: getFillColor(element, defaultFillColor, attrs.fill), fillOpacity: DEFAULT_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); } function renderAssociation(parentGfx, element, attrs = {}) { var semantic = getSemantic(element); var fill = getFillColor(element, defaultFillColor, attrs.fill), stroke = getStrokeColor(element, defaultStrokeColor, attrs.stroke); if (semantic.get('associationDirection') === 'One' || semantic.get('associationDirection') === 'Both') { attrs.markerEnd = marker(parentGfx, 'association-end', fill, stroke); } if (semantic.get('associationDirection') === 'Both') { attrs.markerStart = marker(parentGfx, 'association-start', fill, stroke); } attrs = pickAttrs(attrs, [ 'markerStart', 'markerEnd' ]); return drawConnectionSegments(parentGfx, element.waypoints, { ...attrs, stroke, strokeDasharray: '0, 5' }); } function renderDataObject(parentGfx, element, attrs = {}) { var fill = getFillColor(element, defaultFillColor, attrs.fill), stroke = getStrokeColor(element, defaultStrokeColor, attrs.stroke); var pathData = pathMap.getScaledPath('DATA_OBJECT_PATH', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.474, my: 0.296 } }); var dataObject = drawPath(parentGfx, pathData, { fill, fillOpacity: DEFAULT_OPACITY, stroke }); var semantic = getSemantic(element); if (isCollection(semantic)) { var collectionPathData = pathMap.getScaledPath('DATA_OBJECT_COLLECTION_PATH', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.33, my: (element.height - 18) / element.height } }); drawPath(parentGfx, collectionPathData, { strokeWidth: 2, fill, stroke }); } return dataObject; } function renderEvent(parentGfx, element, attrs = {}) { return drawCircle(parentGfx, element.width, element.height, { fillOpacity: DEFAULT_OPACITY, ...attrs, fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); } function renderGateway(parentGfx, element, attrs = {}) { return drawDiamond(parentGfx, element.width, element.height, { fill: getFillColor(element, defaultFillColor, attrs.fill), fillOpacity: DEFAULT_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); } function renderLane(parentGfx, element, attrs = {}) { var lane = drawRect(parentGfx, getWidth(element, attrs), getHeight(element, attrs), 0, { fill: getFillColor(element, defaultFillColor, attrs.fill), fillOpacity: attrs.fillOpacity || DEFAULT_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1.5 }); var semantic = getSemantic(element); if (is(semantic, 'bpmn:Lane')) { var text = semantic.get('name'); renderLaneLabel(parentGfx, text, element, attrs); } return lane; } function renderSubProcess(parentGfx, element, attrs = {}) { var activity = renderActivity(parentGfx, element, attrs); var expanded = isExpanded(element); if (isEventSubProcess(element)) { svgAttr(activity, { strokeDasharray: '0, 5.5', strokeWidth: 2.5 }); if (!expanded) { var flowElements = getSemantic(element).flowElements || []; var startEvents = flowElements.filter(e => is(e, 'bpmn:StartEvent')); if (startEvents.length === 1) { renderEventSubProcessIcon(startEvents[0], parentGfx, attrs, element); } } } renderEmbeddedLabel(parentGfx, element, expanded ? 'center-top' : 'center-middle', attrs); if (expanded) { renderTaskMarkers(parentGfx, element, undefined, attrs); } else { renderTaskMarkers(parentGfx, element, [ 'SubProcessMarker' ], attrs); } return activity; } function renderEventSubProcessIcon(startEvent, parentGfx, attrs, proxyElement) { var iconSize = 22; // match the colors of the enclosing subprocess var proxyAttrs = { fill: getFillColor(proxyElement, defaultFillColor, attrs.fill), stroke: getStrokeColor(proxyElement, defaultStrokeColor, attrs.stroke), width: iconSize, height: iconSize }; var interrupting = getSemantic(startEvent).isInterrupting; var strokeDasharray = interrupting ? 0 : 3; // better visibility for non-interrupting events var strokeWidth = interrupting ? 1 : 1.2; // make the icon look larger by drawing a smaller circle var circleSize = 20; var shift = (iconSize - circleSize) / 2; var transform = 'translate(' + shift + ',' + shift + ')'; drawCircle(parentGfx, circleSize, circleSize, { fill: proxyAttrs.fill, stroke: proxyAttrs.stroke, strokeWidth, strokeDasharray, transform }); renderEventIcon(startEvent, parentGfx, proxyAttrs, proxyElement); } function renderTask(parentGfx, element, attrs = {}) { var activity = renderActivity(parentGfx, element, attrs); renderEmbeddedLabel(parentGfx, element, 'center-middle', attrs); renderTaskMarkers(parentGfx, element, undefined, attrs); return activity; } var handlers = this.handlers = { 'bpmn:AdHocSubProcess': function(parentGfx, element, attrs = {}) { if (isExpanded(element)) { attrs = pickAttrs(attrs, [ 'fill', 'stroke', 'width', 'height' ]); } else { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); } return renderSubProcess(parentGfx, element, attrs); }, 'bpmn:Association': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); return renderAssociation(parentGfx, element, attrs); }, 'bpmn:BoundaryEvent': function(parentGfx, element, attrs = {}) { var { renderIcon = true } = attrs; attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var semantic = getSemantic(element), cancelActivity = semantic.get('cancelActivity'); attrs = { strokeWidth: 1.5, fill: getFillColor(element, defaultFillColor, attrs.fill), fillOpacity: FULL_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }; if (!cancelActivity) { attrs.strokeDasharray = '6'; } var event = renderEvent(parentGfx, element, attrs); drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, { ...attrs, fill: 'none' }); if (renderIcon) { renderEventIcon(element, parentGfx, attrs); } return event; }, 'bpmn:BusinessRuleTask': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var task = renderTask(parentGfx, element, attrs); var headerData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_MAIN', { abspos: { x: 8, y: 8 } }); var businessPath = drawPath(parentGfx, headerData); svgAttr(businessPath, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); var headerPathData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_HEADER', { abspos: { x: 8, y: 8 } }); var businessHeaderPath = drawPath(parentGfx, headerPathData); svgAttr(businessHeaderPath, { fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); return task; }, 'bpmn:CallActivity': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); return renderSubProcess(parentGfx, element, { strokeWidth: 5, ...attrs }); }, 'bpmn:ComplexGateway': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var gateway = renderGateway(parentGfx, element, attrs); var pathData = pathMap.getScaledPath('GATEWAY_COMPLEX', { xScaleFactor: 0.5, yScaleFactor:0.5, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.46, my: 0.26 } }); drawPath(parentGfx, pathData, { fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); return gateway; }, 'bpmn:DataInput': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var arrowPathData = pathMap.getRawPath('DATA_ARROW'); var dataObject = renderDataObject(parentGfx, element, attrs); drawPath(parentGfx, arrowPathData, { fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); return dataObject; }, 'bpmn:DataInputAssociation': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); return renderAssociation(parentGfx, element, { ...attrs, markerEnd: marker(parentGfx, 'association-end', getFillColor(element, defaultFillColor, attrs.fill), getStrokeColor(element, defaultStrokeColor, attrs.stroke)) }); }, 'bpmn:DataObject': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); return renderDataObject(parentGfx, element, attrs); }, 'bpmn:DataObjectReference': as('bpmn:DataObject'), 'bpmn:DataOutput': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var arrowPathData = pathMap.getRawPath('DATA_ARROW'); var dataObject = renderDataObject(parentGfx, element, attrs); drawPath(parentGfx, arrowPathData, { strokeWidth: 1, fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke) }); return dataObject; }, 'bpmn:DataOutputAssociation': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); return renderAssociation(parentGfx, element, { ...attrs, markerEnd: marker(parentGfx, 'association-end', getFillColor(element, defaultFillColor, attrs.fill), getStrokeColor(element, defaultStrokeColor, attrs.stroke)) }); }, 'bpmn:DataStoreReference': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var dataStorePath = pathMap.getScaledPath('DATA_STORE', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: 0, my: 0.133 } }); return drawPath(parentGfx, dataStorePath, { fill: getFillColor(element, defaultFillColor, attrs.fill), fillOpacity: DEFAULT_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 2 }); }, 'bpmn:EndEvent': function(parentGfx, element, attrs = {}) { var { renderIcon = true } = attrs; attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var event = renderEvent(parentGfx, element, { ...attrs, strokeWidth: 4 }); if (renderIcon) { renderEventIcon(element, parentGfx, attrs); } return event; }, 'bpmn:EventBasedGateway': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var semantic = getSemantic(element); var diamond = renderGateway(parentGfx, element, attrs); drawCircle(parentGfx, element.width, element.height, element.height * 0.20, { fill: getFillColor(element, 'none', attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); var type = semantic.get('eventGatewayType'), instantiate = !!semantic.get('instantiate'); function drawEvent() { var pathData = pathMap.getScaledPath('GATEWAY_EVENT_BASED', { xScaleFactor: 0.18, yScaleFactor: 0.18, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.36, my: 0.44 } }); drawPath(parentGfx, pathData, { fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 2 }); } if (type === 'Parallel') { var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', { xScaleFactor: 0.4, yScaleFactor: 0.4, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.474, my: 0.296 } }); drawPath(parentGfx, pathData, { fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); } else if (type === 'Exclusive') { if (!instantiate) { drawCircle(parentGfx, element.width, element.height, element.height * 0.26, { fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); } drawEvent(); } return diamond; }, 'bpmn:ExclusiveGateway': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var gateway = renderGateway(parentGfx, element, attrs); var pathData = pathMap.getScaledPath('GATEWAY_EXCLUSIVE', { xScaleFactor: 0.4, yScaleFactor: 0.4, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.32, my: 0.3 } }); var di = getDi(element); if (di.get('isMarkerVisible')) { drawPath(parentGfx, pathData, { fill: getStrokeColor(element, defaultStrokeColor, attrs.stroke), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1 }); } return gateway; }, 'bpmn:Gateway': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); return renderGateway(parentGfx, element, attrs); }, 'bpmn:Group': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke', 'width', 'height' ]); return drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, { stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1.5, strokeDasharray: '10, 6, 0, 6', fill: 'none', pointerEvents: 'none', width: getWidth(element, attrs), height: getHeight(element, attrs) }); }, 'bpmn:InclusiveGateway': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var gateway = renderGateway(parentGfx, element, attrs); drawCircle(parentGfx, element.width, element.height, element.height * 0.24, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 2.5 }); return gateway; }, 'bpmn:IntermediateEvent': function(parentGfx, element, attrs = {}) { var { renderIcon = true } = attrs; attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var outer = renderEvent(parentGfx, element, { ...attrs, strokeWidth: 1.5 }); drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, { fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 1.5 }); if (renderIcon) { renderEventIcon(element, parentGfx, attrs); } return outer; }, 'bpmn:IntermediateCatchEvent': as('bpmn:IntermediateEvent'), 'bpmn:IntermediateThrowEvent': as('bpmn:IntermediateEvent'), 'bpmn:Lane': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke', 'width', 'height' ]); return renderLane(parentGfx, element, { ...attrs, fillOpacity: LOW_OPACITY }); }, 'bpmn:ManualTask': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var task = renderTask(parentGfx, element, attrs); var pathData = pathMap.getScaledPath('TASK_TYPE_MANUAL', { abspos: { x: 17, y: 15 } }); drawPath(parentGfx, pathData, { fill: getFillColor(element, defaultFillColor, attrs.fill), stroke: getStrokeColor(element, defaultStrokeColor, attrs.stroke), strokeWidth: 0.5 }); return task; }, 'bpmn:MessageFlow': function(parentGfx, element, attrs = {}) { attrs = pickAttrs(attrs, [ 'fill', 'stroke' ]); var semantic = getSemantic(element), di = getDi(element); var fill = getFillColor(element, defaultFillColor, attrs.fill), stroke = getStrokeColor(element, defaultStrokeColor, attrs.stroke); var path = drawConnectionSegments(parentGfx, element.waypoints, { markerEnd: marker(parentGfx, 'messageflow-end', fill, stroke), markerStart: marker(parentGfx, 'messageflow-start', fill, stroke), stroke, strokeDasharray: '10, 11', strokeWidth: 1.5 }); if (semantic.get('messageRef')) { var midPoint = path.getPointAtLength(path.getTotalLength() / 2); var markerPathData = pathMap.getScaledPath('MESSAGE_FLOW_MARKER', { abspos: { x: midPoint.x, y: midPoint.y } }); var messageAttrs = { strokeWidth: 1 }; if (di.get('messageVisibleKind') === 'initiating') { messageAttrs.fill = fill; messageAttrs.stroke = stroke; } else { messageAttrs.fill = stroke; messageAttrs.stroke = fill; } var message = drawPath(parentGfx, markerPathData, messageAttrs); var messageRef = semantic.get('messageRef'), name = messageRef.get('name'); var label = renderLabel(parentGfx, name, { align: 'center-top', fitBox: true, style: { fill: stroke } }); var messageBounds = message.getBBox(), labelBounds = label.getBBox(); var translateX = midPoint.x - labelBounds.width / 2, translateY = midPoint.y + messageBounds.height / 2 + ELEMENT_LABEL_DISTANCE; transform(label, translateX, translateY, 0); } return path; }, 'bpmn:ParallelGateway': function(parentGfx, element,