feat(labels): render external / internal labels

This commit adds internal + external label rendering for

* activity
* event
* gateway
* sequenceFlow
* subProcess
* transaction

Closes #16
This commit is contained in:
Nico Rehwaldt 2014-04-25 16:14:36 +02:00
parent 1369fb5ad6
commit e8b70ebc83
7 changed files with 366 additions and 27 deletions

View File

@ -34,7 +34,7 @@ function initListeners(diagram, listeners) {
* @class
*
* A viewer for BPMN 2.0 diagrams
*
*
* @param {Object} [options] configuration options to pass to the viewer
* @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
* @param {String|Number} [options.width] the width of the viewer
@ -142,12 +142,14 @@ Viewer.prototype.get = function(name) {
Viewer.prototype.importDefinitions = failSafeAsync(function(definitions, done) {
if (this.diagram) {
var diagram = this.diagram;
if (diagram) {
this.clear();
}
var diagram = this.createDiagram();
diagram = this.createDiagram();
this.initDiagram(diagram);
this.definitions = definitions;
@ -179,8 +181,15 @@ Viewer.prototype.clear = function() {
};
Viewer.prototype.on = function(event, handler) {
this.__listeners = this.__listeners || [];
this.__listeners.push({ event: event, handler: handler });
var diagram = this.diagram,
listeners = this.__listeners = this.__listeners || [];
listeners = this.__listeners || [];
listeners.push({ event: event, handler: handler });
if (diagram) {
diagram.get('eventBus').on(event, handler);
}
};
module.exports = Viewer;

View File

@ -5,10 +5,11 @@ require('diagram-js/lib/core/EventBus');
require('diagram-js/lib/draw/Styles');
require('../core/BpmnRegistry');
require('./PathMap');
require('./PathMap')
var DefaultRenderer = require('diagram-js/lib/draw/Renderer');
var LabelUtil = require('diagram-js/lib/util/LabelUtil');
var flattenPoints = DefaultRenderer.flattenPoints;
@ -17,9 +18,18 @@ function BpmnRenderer(events, styles, bpmnRegistry, pathMap) {
DefaultRenderer.apply(this, [ events, styles ]);
var TASK_BORDER_RADIUS = 8;
var TASK_BORDER_RADIUS = 10;
var INNER_OUTER_DIST = 3;
var LABEL_STYLE = {
fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif',
fontSize: '13px'
};
var labelUtil = new LabelUtil({
style: LABEL_STYLE
});
var markers = {};
function addMarker(id, element) {
@ -250,6 +260,21 @@ function BpmnRenderer(events, styles, bpmnRegistry, pathMap) {
return null;
}
function renderLabel(p, label, box, align) {
return labelUtil.createLabel(p, label || '', { box: box, align: align }).addClass('djs-label');
}
function renderEmbeddedLabel(p, data, align) {
var element = bpmnRegistry.getSemantic(data);
return renderLabel(p, element.name, data, align);
}
function renderExternalLabel(p, data, align) {
var element = bpmnRegistry.getSemantic(data.attachedId);
return renderLabel(p, element.name, data, align);
}
var handlers = {
'bpmn:Event': function(p, data) {
var circle = drawCircle(p, data.width, data.height);
@ -553,30 +578,43 @@ function BpmnRenderer(events, styles, bpmnRegistry, pathMap) {
'bpmn:Activity': function(p, data) {
var rect = drawRect(p, data.width, data.height, TASK_BORDER_RADIUS);
return rect;
},
'bpmn:Task': as('bpmn:Activity'),
'bpmn:ServiceTask': as('bpmn:Activity'),
'bpmn:UserTask': as('bpmn:Activity'),
'bpmn:ManualTask': as('bpmn:Activity'),
'bpmn:SendTask': as('bpmn:Activity'),
'bpmn:ReceiveTask': as('bpmn:Activity'),
'bpmn:ScriptTask': as('bpmn:Activity'),
'bpmn:BusinessRuleTask': as('bpmn:Activity'),
'bpmn:SubProcess': as('bpmn:Activity'),
'bpmn:AdHocSubProcess': as('bpmn:Activity'),
'bpmn:Transaction': function(p, data) {
var outer = renderer('bpmn:Activity')(p, data);
var inner = drawRect(p, data.width, data.height, TASK_BORDER_RADIUS - 2, INNER_OUTER_DIST);
'bpmn:Task': function(p, data) {
var rect = renderer('bpmn:Activity')(p, data);
renderEmbeddedLabel(p, data, 'center-middle');
return rect;
},
'bpmn:ServiceTask': as('bpmn:Task'),
'bpmn:UserTask': as('bpmn:Task'),
'bpmn:ManualTask': as('bpmn:Task'),
'bpmn:SendTask': as('bpmn:Task'),
'bpmn:ReceiveTask': as('bpmn:Task'),
'bpmn:ScriptTask': as('bpmn:Task'),
'bpmn:BusinessRuleTask': as('bpmn:Task'),
'bpmn:SubProcess': function(p, data) {
var rect = renderer('bpmn:Activity')(p, data);
outer.attr('stroke-width', 1.5);
inner.attr('stroke-width', 1.5);
var di = bpmnRegistry.getDi(data);
renderEmbeddedLabel(p, data, di.isExpanded ? 'center-top' : 'center-middle');
return rect;
},
'bpmn:AdHocSubProcess': as('bpmn:SubProcess'),
'bpmn:Transaction': function(p, data) {
var outer = renderer('bpmn:SubProcess')(p, data);
var inner = drawRect(p, data.width, data.height, TASK_BORDER_RADIUS - 2, INNER_OUTER_DIST)
.attr(styles.style([ 'no-fill', 'no-events' ]));
outer.attr('stroke-width', 1);
inner.attr('stroke-width', 1);
return outer;
},
'bpmn:CallActivity': function(p, data) {
var rect = renderer('bpmn:Activity')(p, data);
var rect = renderer('bpmn:SubProcess')(p, data);
rect.attr('stroke-width', 4);
return rect;
},
@ -724,7 +762,7 @@ function BpmnRenderer(events, styles, bpmnRegistry, pathMap) {
});
drawEvent();
}
return diamond;
},
@ -875,6 +913,9 @@ function BpmnRenderer(events, styles, bpmnRegistry, pathMap) {
});
return group;
},
'label': function(p, data) {
return renderExternalLabel(p, data, 'center-top');
}
};

View File

@ -3,11 +3,94 @@ var _ = require('lodash');
var BpmnTreeWalker = require('./BpmnTreeWalker'),
Util = require('../Util');
function hasLabel(element) {
return element.$instanceOf('bpmn:Event') ||
element.$instanceOf('bpmn:Gateway') ||
element.$instanceOf('bpmn:DataStoreReference') ||
element.$instanceOf('bpmn:DataObjectReference') ||
element.$instanceOf('bpmn:SequenceFlow');
}
function getWaypointsMid(waypoints) {
var mid = waypoints.length / 2 - 1;
var first = waypoints[Math.floor(mid)];
var second = waypoints[Math.ceil(mid + 0.01)];
return {
x: first.x + (second.x - first.x) / 2,
y: first.y + (second.y - first.y) / 2
};
}
function getLabelBounds(di, data) {
var mid,
size;
var label = di.label;
if (label && label.bounds) {
var bounds = label.bounds;
size = {
width: Math.max(150, bounds.width),
height: bounds.height
};
mid = {
x: bounds.x + bounds.width / 2,
y: bounds.y
};
} else {
if (data.waypoints) {
mid = getWaypointsMid(data.waypoints);
} else {
mid = {
x: data.x + data.width / 2,
y: data.y + data.height + 5
};
}
size = {
width: 150,
height: 50
};
}
return _.extend({
x: mid.x - size.width / 2,
y: mid.y
}, size);
}
function importBpmnDiagram(diagram, definitions, done) {
var canvas = diagram.get('canvas');
var events = diagram.get('eventBus');
function addLabel(element, di, data) {
if (!hasLabel(element)) {
return;
}
var labelBounds = getLabelBounds(di, data);
canvas.addShape(_.extend({
id: element.id + '_label',
attachedId: element.id,
type: 'label'
}, labelBounds));
}
var visitor = {
element: function(element, di, parent) {
@ -46,9 +129,12 @@ function importBpmnDiagram(diagram, definitions, done) {
fire('added', shape);
// add label if needed
addLabel(element, di, shape);
return shape;
},
error: function(message, context) {
console.warn('[import]', message, context);
}
@ -56,7 +142,7 @@ function importBpmnDiagram(diagram, definitions, done) {
var walker = new BpmnTreeWalker(visitor);
walker.handleDefinitions(definitions);
done();
}

40
test/fixtures/bpmn/labels/embedded.bpmn vendored Normal file
View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" id="test" targetNamespace="http://activiti.org/bpmn">
<bpmn2:process id="Process_1" isExecutable="false">
<bpmn2:ioSpecification id="InputOutputSpecification_1"/>
<bpmn2:task id="Task_1" name="A task"/>
<bpmn2:callActivity id="CallActivity_1" name="a call activity"/>
<bpmn2:subProcess id="SubProcess_1" name="an expanded subprocess"/>
<bpmn2:transaction id="Transaction_1" name="a transaction"/>
<bpmn2:subProcess id="SubProcess_2" name="a collapsed&#xD;&#xA;subprocess"/>
<bpmn2:dataObject id="DataObject_1" name="Data Object 1"/>
<bpmn2:textAnnotation id="TextAnnotation_1">
<bpmn2:text><![CDATA[Lorem ipsum
foo
bar]]></bpmn2:text>
</bpmn2:textAnnotation>
</bpmn2:process>
<bpmn2:dataStore id="DataStore_1" name="Data Store 1"/>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="_BPMNShape_TextAnnotation_2" bpmnElement="TextAnnotation_1">
<dc:Bounds height="72.0" width="95.0" x="725.0" y="306.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_SubProcess_2" bpmnElement="SubProcess_1" isExpanded="true">
<dc:Bounds height="169.0" width="220.0" x="204.0" y="306.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Task_5" bpmnElement="Task_1">
<dc:Bounds height="80.0" width="100.0" x="204.0" y="96.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_CallActivity_2" bpmnElement="CallActivity_1">
<dc:Bounds height="80.0" width="100.0" x="324.0" y="96.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Transaction_2" bpmnElement="Transaction_1" isExpanded="true">
<dc:Bounds height="169.0" width="220.0" x="444.0" y="306.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_SubProcess_3" bpmnElement="SubProcess_2">
<dc:Bounds height="80.0" width="100.0" x="444.0" y="96.0"/>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" id="test" targetNamespace="http://activiti.org/bpmn">
<bpmn2:process id="Process_1" isExecutable="false">
<bpmn2:ioSpecification id="InputOutputSpecification_1"/>
<bpmn2:dataStoreReference id="_DataStoreReference_2" name="a data store" dataStoreRef="DataStore_1"/>
<bpmn2:dataObject id="DataObject_1" name="Data Object 1"/>
<bpmn2:intermediateThrowEvent id="IntermediateThrowEvent_1" name="a throw event">
<bpmn2:incoming>SequenceFlow_1</bpmn2:incoming>
</bpmn2:intermediateThrowEvent>
<bpmn2:startEvent id="StartEvent_1" name="a start event">
<bpmn2:outgoing>SequenceFlow_1</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:sequenceFlow id="SequenceFlow_1" name="a sequence flow" sourceRef="StartEvent_1" targetRef="IntermediateThrowEvent_1"/>
<bpmn2:exclusiveGateway id="ExclusiveGateway_1" name="a gateway"/>
<bpmn2:dataObjectReference id="DataObjectReference_1" name="a data object&#xD;&#xA;reference" dataObjectRef="DataObject_1"/>
</bpmn2:process>
<bpmn2:dataStore id="DataStore_1" name="Data Store 1"/>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="_BPMNShape_ExclusiveGateway_3" bpmnElement="ExclusiveGateway_1" isMarkerVisible="true">
<dc:Bounds height="50.0" width="50.0" x="84.0" y="59.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_DataStoreReference_2" bpmnElement="_DataStoreReference_2">
<dc:Bounds height="50.0" width="50.0" x="192.0" y="60.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_DataObjectReference_2" bpmnElement="DataObjectReference_1">
<dc:Bounds height="50.0" width="36.0" x="312.0" y="60.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_3" bpmnElement="StartEvent_1">
<dc:Bounds height="36.0" width="36.0" x="424.0" y="237.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_IntermediateThrowEvent_2" bpmnElement="IntermediateThrowEvent_1">
<dc:Bounds height="36.0" width="36.0" x="624.0" y="337.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_1" bpmnElement="SequenceFlow_1" sourceElement="_BPMNShape_StartEvent_3" targetElement="_BPMNShape_IntermediateThrowEvent_2">
<di:waypoint xsi:type="dc:Point" x="460.0" y="255.0"/>
<di:waypoint xsi:type="dc:Point" x="542.0" y="255.0"/>
<di:waypoint xsi:type="dc:Point" x="542.0" y="355.0"/>
<di:waypoint xsi:type="dc:Point" x="624.0" y="355.0"/>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

61
test/fixtures/bpmn/labels/external.bpmn vendored Normal file
View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" id="test" targetNamespace="http://activiti.org/bpmn">
<bpmn2:process id="Process_1" isExecutable="false">
<bpmn2:ioSpecification id="InputOutputSpecification_1"/>
<bpmn2:dataStoreReference id="_DataStoreReference_2" name="a data store" dataStoreRef="DataStore_1"/>
<bpmn2:dataObject id="DataObject_1" name="Data Object 1"/>
<bpmn2:intermediateThrowEvent id="IntermediateThrowEvent_1" name="a throw event">
<bpmn2:incoming>SequenceFlow_1</bpmn2:incoming>
</bpmn2:intermediateThrowEvent>
<bpmn2:startEvent id="StartEvent_1" name="a start event">
<bpmn2:outgoing>SequenceFlow_1</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:sequenceFlow id="SequenceFlow_1" name="a sequence flow" sourceRef="StartEvent_1" targetRef="IntermediateThrowEvent_1"/>
<bpmn2:exclusiveGateway id="ExclusiveGateway_1" name="a gateway"/>
<bpmn2:dataObjectReference id="DataObjectReference_1" name="a data object&#xD;&#xA;reference" dataObjectRef="DataObject_1"/>
</bpmn2:process>
<bpmn2:dataStore id="DataStore_1" name="Data Store 1"/>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="_BPMNShape_ExclusiveGateway_3" bpmnElement="ExclusiveGateway_1" isMarkerVisible="true">
<dc:Bounds height="50.0" width="50.0" x="84.0" y="59.0"/>
<bpmndi:BPMNLabel>
<dc:Bounds height="22.0" width="65.0" x="78.0" y="28.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_DataStoreReference_2" bpmnElement="_DataStoreReference_2">
<dc:Bounds height="50.0" width="50.0" x="192.0" y="60.0"/>
<bpmndi:BPMNLabel>
<dc:Bounds height="22.0" width="75.0" x="180.0" y="28.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_DataObjectReference_2" bpmnElement="DataObjectReference_1">
<dc:Bounds height="50.0" width="36.0" x="312.0" y="60.0"/>
<bpmndi:BPMNLabel>
<dc:Bounds height="38.0" width="91.0" x="285.0" y="12.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_3" bpmnElement="StartEvent_1">
<dc:Bounds height="36.0" width="36.0" x="424.0" y="237.0"/>
<bpmndi:BPMNLabel>
<dc:Bounds height="22.0" width="77.0" x="404.0" y="204.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_IntermediateThrowEvent_2" bpmnElement="IntermediateThrowEvent_1">
<dc:Bounds height="36.0" width="36.0" x="624.0" y="337.0"/>
<bpmndi:BPMNLabel>
<dc:Bounds height="22.0" width="82.0" x="601.0" y="308.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_1" bpmnElement="SequenceFlow_1" sourceElement="_BPMNShape_StartEvent_3" targetElement="_BPMNShape_IntermediateThrowEvent_2">
<di:waypoint xsi:type="dc:Point" x="460.0" y="255.0"/>
<di:waypoint xsi:type="dc:Point" x="542.0" y="255.0"/>
<di:waypoint xsi:type="dc:Point" x="542.0" y="355.0"/>
<di:waypoint xsi:type="dc:Point" x="624.0" y="355.0"/>
<bpmndi:BPMNLabel>
<dc:Bounds height="22.0" width="99.0" x="432.0" y="317.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@ -0,0 +1,59 @@
var fs = require('fs');
var Viewer = require('../../../../lib/Viewer');
var Matchers = require('../../Matchers');
describe('import - labels', function() {
beforeEach(Matchers.add);
var container;
beforeEach(function() {
container = document.createElement('div');
document.getElementsByTagName('body')[0].appendChild(container);
});
it('should import embedded labels', function(done) {
var xml = fs.readFileSync('test/fixtures/bpmn/labels/embedded.bpmn', 'utf8');
var renderer = new Viewer(container);
renderer.importXML(xml, function(err) {
done(err);
});
});
describe('should import external labels', function() {
it('with di', function(done) {
var xml = fs.readFileSync('test/fixtures/bpmn/labels/external.bpmn', 'utf8');
var renderer = new Viewer(container);
renderer.importXML(xml, function(err) {
done(err);
});
});
it('without di', function(done) {
var xml = fs.readFileSync('test/fixtures/bpmn/labels/external-no-di.bpmn', 'utf8');
var renderer = new Viewer(container);
renderer.importXML(xml, function(err) {
done(err);
});
});
});
});