feat(layout): add straight layouting for message flows

This commit adds a straight layouting strategy for message flows.
Other than that it makes sure connection attachments are being
remembered during reconnection / shape move.

Closes #249
Closes #179
This commit is contained in:
Nico Rehwaldt 2015-04-22 09:08:38 +02:00
parent 373b8c6293
commit 6eceb0926b
7 changed files with 193 additions and 58 deletions

View File

@ -0,0 +1,65 @@
'use strict';
var inherits = require('inherits');
var assign = require('lodash/object/assign');
var BaseLayouter = require('diagram-js/lib/layout/BaseLayouter'),
LayoutUtil = require('diagram-js/lib/layout/LayoutUtil'),
ManhattanLayout = require('diagram-js/lib/layout/ManhattanLayout');
var is = require('./ModelingUtil').is;
function BpmnLayouter() {}
inherits(BpmnLayouter, BaseLayouter);
module.exports = BpmnLayouter;
function getAttachment(waypoints, idx, shape) {
var point = waypoints && waypoints[idx];
return point ? (point.original || point) : LayoutUtil.getMidPoint(shape);
}
BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
var source = connection.source,
target = connection.target,
waypoints = connection.waypoints,
start,
end;
var layoutManhattan,
updatedWaypoints;
start = getAttachment(waypoints, 0, source);
end = getAttachment(waypoints, waypoints && waypoints.length - 1, target);
// manhattan layout sequence / message flows
if (is(connection, 'bpmn:MessageFlow')) {
layoutManhattan = {
preferStraight: true,
preferVertical: true
};
}
if (is(connection, 'bpmn:SequenceFlow')) {
layoutManhattan = {};
}
if (layoutManhattan) {
layoutManhattan = assign(layoutManhattan, hints);
updatedWaypoints =
ManhattanLayout.repairConnection(
source, target, start, end,
waypoints,
layoutManhattan);
}
return updatedWaypoints || [ start, end ];
};

View File

@ -1,37 +0,0 @@
'use strict';
var inherits = require('inherits');
var BaseLayouter = require('diagram-js/lib/features/modeling/Layouter'),
LayoutUtil = require('diagram-js/lib/layout/Util'),
ManhattanLayout = require('diagram-js/lib/layout/ManhattanLayout');
function Layouter() {}
inherits(Layouter, BaseLayouter);
module.exports = Layouter;
Layouter.prototype.getConnectionWaypoints = function(connection) {
var source = connection.source,
start = LayoutUtil.getMidPoint(source),
target = connection.target,
end = LayoutUtil.getMidPoint(target);
var bo = connection.businessObject;
// manhattan layout sequence / message flows
if (bo.$instanceOf('bpmn:SequenceFlow') ||
bo.$instanceOf('bpmn:MessageFlow')) {
var waypoints = ManhattanLayout.repairConnection(source, target, start, end, connection.waypoints);
if (waypoints) {
return waypoints;
}
}
return [ start, end ];
};

View File

@ -12,6 +12,6 @@ module.exports = {
elementFactory: [ 'type', require('./ElementFactory') ],
modeling: [ 'type', require('./Modeling') ],
labelSupport: [ 'type', require('./LabelSupport') ],
layouter: [ 'type', require('./Layouter') ],
layouter: [ 'type', require('./BpmnLayouter') ],
connectionDocking: [ 'type', require('diagram-js/lib/layout/CroppingConnectionDocking') ]
};

View File

@ -1,9 +1,9 @@
<?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="_pHDz0KojEeOJhIBv1RySdg" targetNamespace="http://bpmn.io/schema/bpmn">
<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="_pHDz0KojEeOJhIBv1RySdg" exporter="camunda modeler" exporterVersion="2.6.0" targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn2:collaboration id="_Collaboration_2">
<bpmn2:participant id="Participant_2" name="Pool" processRef="Process_1"/>
<bpmn2:participant id="Participant_1" name="Pool" processRef="Process_2"/>
<bpmn2:messageFlow id="MessageFlow_1" name="" sourceRef="Task_1" targetRef="Participant_1"/>
<bpmn2:messageFlow id="MessageFlow_1" name="" sourceRef="Task_2" targetRef="Participant_1"/>
<bpmn2:messageFlow id="MessageFlow_2" name="" sourceRef="Participant_1" targetRef="Participant_2"/>
<bpmn2:messageFlow id="MessageFlow_4" name="" sourceRef="Task_1" targetRef="StartEvent_1"/>
<bpmn2:messageFlow id="MessageFlow_5" name="" sourceRef="EndEvent_1" targetRef="Participant_1"/>
@ -13,6 +13,7 @@
<bpmn2:endEvent id="EndEvent_1">
<bpmn2:messageEventDefinition id="MessageEventDefinition_2"/>
</bpmn2:endEvent>
<bpmn2:task id="Task_2"/>
</bpmn2:process>
<bpmn2:process id="Process_2" isExecutable="false">
<bpmn2:startEvent id="StartEvent_1">
@ -34,20 +35,18 @@
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Task_3" bpmnElement="Task_1">
<dc:Bounds height="80.0" width="100.0" x="404.0" y="135.0"/>
<dc:Bounds height="80.0" width="100.0" x="360.0" y="134.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_MessageFlow_1" bpmnElement="MessageFlow_1" sourceElement="_BPMNShape_Task_3" targetElement="_BPMNShape_Participant_3">
<di:waypoint xsi:type="dc:Point" x="454.0" y="215.0"/>
<di:waypoint xsi:type="dc:Point" x="454.0" y="370.0"/>
<di:waypoint xsi:type="dc:Point" x="522.0" y="370.0"/>
<di:waypoint xsi:type="dc:Point" x="522.0" y="415.0"/>
<bpmndi:BPMNEdge id="BPMNEdge_MessageFlow_1" bpmnElement="MessageFlow_1" sourceElement="_BPMNShape_Task_4" targetElement="_BPMNShape_Participant_3">
<di:waypoint xsi:type="dc:Point" x="590.0" y="214.0"/>
<di:waypoint xsi:type="dc:Point" x="590.0" y="415.0"/>
<bpmndi:BPMNLabel>
<dc:Bounds height="6.0" width="6.0" x="451.0" y="316.0"/>
<dc:Bounds height="6.0" width="6.0" x="522.0" y="314.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_MessageFlow_2" bpmnElement="MessageFlow_2" sourceElement="_BPMNShape_Participant_3" targetElement="_BPMNShape_Participant_2">
<di:waypoint xsi:type="dc:Point" x="522.0" y="415.0"/>
<di:waypoint xsi:type="dc:Point" x="522.0" y="386.0"/>
<di:waypoint xsi:type="dc:Point" x="506.0" y="415.0"/>
<di:waypoint xsi:type="dc:Point" x="506.0" y="386.0"/>
<di:waypoint xsi:type="dc:Point" x="506.0" y="386.0"/>
<di:waypoint xsi:type="dc:Point" x="506.0" y="300.0"/>
<bpmndi:BPMNLabel>
@ -55,12 +54,12 @@
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_MessageFlow_4" bpmnElement="MessageFlow_4" sourceElement="_BPMNShape_Task_3" targetElement="_BPMNShape_StartEvent_3">
<di:waypoint xsi:type="dc:Point" x="454.0" y="215.0"/>
<di:waypoint xsi:type="dc:Point" x="454.0" y="387.0"/>
<di:waypoint xsi:type="dc:Point" x="390.0" y="214.0"/>
<di:waypoint xsi:type="dc:Point" x="390.0" y="387.0"/>
<di:waypoint xsi:type="dc:Point" x="318.0" y="387.0"/>
<di:waypoint xsi:type="dc:Point" x="318.0" y="448.0"/>
<bpmndi:BPMNLabel>
<dc:Bounds height="6.0" width="6.0" x="451.0" y="322.0"/>
<dc:Bounds height="6.0" width="6.0" x="427.0" y="321.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_EndEvent_2" bpmnElement="EndEvent_1">
@ -71,13 +70,14 @@
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_MessageFlow_5" bpmnElement="MessageFlow_5" sourceElement="_BPMNShape_EndEvent_2" targetElement="_BPMNShape_Participant_3">
<di:waypoint xsi:type="dc:Point" x="671.0" y="214.0"/>
<di:waypoint xsi:type="dc:Point" x="671.0" y="332.0"/>
<di:waypoint xsi:type="dc:Point" x="522.0" y="332.0"/>
<di:waypoint xsi:type="dc:Point" x="522.0" y="415.0"/>
<di:waypoint xsi:type="dc:Point" x="671.0" y="415.0"/>
<bpmndi:BPMNLabel>
<dc:Bounds height="6.0" width="6.0" x="630.0" y="332.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_Task_4" bpmnElement="Task_2">
<dc:Bounds height="80.0" width="100.0" x="516.0" y="134.0"/>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@ -36,8 +36,9 @@ describe('features/modeling - layout connection', function() {
// then
// expect cropped, repaired connection
// that was not actually modified
expect(sequenceFlowConnection.waypoints).toDeepEqual([
{ original: { x: 553, y: 341 }, x: 578, y: 341 },
{ original: { x: 578, y: 341 }, x: 578, y: 341 },
{ x: 934, y: 341 },
{ x: 934, y: 436 },
{ original: { x: 832, y: 436 }, x: 832, y: 436 }

View File

@ -0,0 +1,106 @@
'use strict';
var Matchers = require('../../../Matchers'),
TestHelper = require('../../../TestHelper');
/* global bootstrapModeler, inject */
var modelingModule = require('../../../../lib/features/modeling'),
coreModule = require('../../../../lib/core');
describe('features/modeling - layout message flows', function() {
beforeEach(Matchers.addDeepEquals);
var diagramXML = require('../../../fixtures/bpmn/collaboration-message-flows.bpmn');
var testModules = [ coreModule, modelingModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
it('should layout manhattan after Task move', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_1'),
messageFlowConnection = elementRegistry.get('MessageFlow_4');
// when
modeling.moveShapes([ taskShape ], { x: 30, y: 20 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection.waypoints).toDeepEqual([
{ original: { x: 420, y: 234 }, x: 420, y: 234 },
{ x: 420, y: 387 },
{ x: 318, y: 387 },
{ original: { x: 318, y: 448 }, x: 318, y: 448 }
]);
}));
it('should layout straight after Task move', inject(function(elementRegistry, modeling) {
// given
var taskShape = elementRegistry.get('Task_2'),
messageFlowConnection = elementRegistry.get('MessageFlow_1');
// when
modeling.moveShapes([ taskShape ], { x: 20, y: -20 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection.waypoints).toDeepEqual([
{ original: { x: 610, y: 194 }, x: 610, y: 194 },
{ original: { x: 610, y: 415 }, x: 610, y: 415 }
]);
}));
it('should layout straight after Participant move', inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_1'),
messageFlowConnection = elementRegistry.get('MessageFlow_5');
// when
modeling.moveShapes([ participantShape ], { x: 100, y: 50 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection.waypoints).toDeepEqual([
{ original: { x: 671, y: 214 }, x: 671, y: 214 },
{ original: { x: 671, y: 465 }, x: 671, y: 465 }
]);
}));
it('should layout manhattan after Participant move beyond EndEvent bounds',
inject(function(elementRegistry, modeling) {
// given
var participantShape = elementRegistry.get('Participant_1'),
messageFlowConnection = elementRegistry.get('MessageFlow_5');
// when
modeling.moveShapes([ participantShape ], { x: -200, y: 0 });
// then
// expect cropped, repaired manhattan connection
expect(messageFlowConnection.waypoints).toDeepEqual([
{ original: { x: 671, y: 214 }, x: 671, y: 214 },
{ x: 671, y: 315 },
{ x: 471, y: 315 },
{ original: { x: 471, y: 415 }, x: 471, y: 415 }
]);
}));
});

View File

@ -47,10 +47,10 @@ describe('features/modeling - move shape', function() {
// expect flow layout
expect(sequenceFlowElement.waypoints).toDeepEqual([
{ original: { x: 370, y: 310 }, x: 388, y: 310 },
{ original: { x: 388, y: 310 }, x: 388, y: 310 },
{ x: 404, y: 310 },
{ x: 404, y: 260 },
{ original: { x: 470, y: 260 }, x: 420, y: 260 }
{ original: { x: 420, y: 260 }, x: 420, y: 260 }
]);
expect(sequenceFlow.di.waypoint).toDeepEqual([