From a8cf097ed0c71ebac53d0de968d05b5715e5c12d Mon Sep 17 00:00:00 2001 From: hoferch91 Date: Wed, 17 Aug 2016 15:36:13 +0200 Subject: [PATCH] feat(modeling): retain waypoints with connection on shape deletion When keeping the connection when the intermittant element, make sure we keep the waypoints, too. closes #581 --- .../modeling/behavior/ImportDockingFix.js | 28 +-- .../behavior/RemoveElementBehavior.js | 42 +++- .../modeling/behavior/util/LabelLayoutUtil.js | 11 +- .../modeling/behavior/util/LineIntersect.js | 37 +++ .../behavior/RemoveElementBehavior.bpmn | 127 +++++++---- .../RemoveElementBehavior.perpendicular.bpmn | 159 +++++++++++++ .../behavior/RemoveElementBehaviorSpec.js | 214 ++++++++++++++++-- .../behavior/util/LineIntersectSpec.js | 33 +++ 8 files changed, 553 insertions(+), 98 deletions(-) create mode 100644 lib/features/modeling/behavior/util/LineIntersect.js create mode 100644 test/spec/features/modeling/behavior/RemoveElementBehavior.perpendicular.bpmn create mode 100644 test/spec/features/modeling/behavior/util/LineIntersectSpec.js diff --git a/lib/features/modeling/behavior/ImportDockingFix.js b/lib/features/modeling/behavior/ImportDockingFix.js index a13ba591..93728c53 100644 --- a/lib/features/modeling/behavior/ImportDockingFix.js +++ b/lib/features/modeling/behavior/ImportDockingFix.js @@ -2,6 +2,8 @@ var getMid = require('diagram-js/lib/layout/LayoutUtil').getMid; +var lineIntersect = require('./util/LineIntersect'); + /** * Fix broken dockings after DI imports. @@ -74,32 +76,6 @@ module.exports = ImportDockingFix; /////// helpers ////////////////////////////////// -function lineIntersect(l1s, l1e, l2s, l2e) { - // if the lines intersect, the result contains the x and y of the - // intersection (treating the lines as infinite) and booleans for - // whether line segment 1 or line segment 2 contain the point - var denominator, a, b, c, numerator; - - denominator = ((l2e.y - l2s.y) * (l1e.x - l1s.x)) - ((l2e.x - l2s.x) * (l1e.y - l1s.y)); - - if (denominator == 0) { - return null; - } - - a = l1s.y - l2s.y; - b = l1s.x - l2s.x; - numerator = ((l2e.x - l2s.x) * a) - ((l2e.y - l2s.y) * b); - - c = numerator / denominator; - - // if we cast these lines infinitely in - // both directions, they intersect here - return { - x: Math.round(l1s.x + (c * (l1e.x - l1s.x))), - y: Math.round(l1s.y + (c * (l1e.y - l1s.y))) - }; -} - function getDistance(p1, p2) { return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); } \ No newline at end of file diff --git a/lib/features/modeling/behavior/RemoveElementBehavior.js b/lib/features/modeling/behavior/RemoveElementBehavior.js index d775816b..94eac0f0 100644 --- a/lib/features/modeling/behavior/RemoveElementBehavior.js +++ b/lib/features/modeling/behavior/RemoveElementBehavior.js @@ -4,6 +4,9 @@ var inherits = require('inherits'); var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor'); +var lineIntersect = require('./util/LineIntersect'); + + function RemoveElementBehavior(eventBus, bpmnRules, modeling) { CommandInterceptor.call(this, eventBus); @@ -13,7 +16,6 @@ function RemoveElementBehavior(eventBus, bpmnRules, modeling) { * if there is one incoming and one outgoing * sequence flow */ - this.preExecute('shape.delete', function(e) { var shape = e.context.shape; @@ -23,14 +25,17 @@ function RemoveElementBehavior(eventBus, bpmnRules, modeling) { var inConnection = shape.incoming[0], outConnection = shape.outgoing[0]; - var docking = outConnection.waypoints[outConnection.waypoints.length - 1]; if (bpmnRules.canConnect(inConnection.source, outConnection.target, inConnection)) { - modeling.reconnectEnd(inConnection, outConnection.target, docking); - } + // compute new, combined waypoints + var newWaypoints = getNewWaypoints(inConnection.waypoints, outConnection.waypoints); + + modeling.reconnectEnd(inConnection, outConnection.target, newWaypoints); + } } }); + } inherits(RemoveElementBehavior, CommandInterceptor); @@ -38,3 +43,32 @@ inherits(RemoveElementBehavior, CommandInterceptor); RemoveElementBehavior.$inject = [ 'eventBus', 'bpmnRules', 'modeling' ]; module.exports = RemoveElementBehavior; + + +///////// helpers ////////////////////////////// + +function getDocking(point) { + return point.original || point; +} + + +function getNewWaypoints(inWaypoints, outWaypoints) { + + var intersection = lineIntersect( + getDocking(inWaypoints[inWaypoints.length - 2]), + getDocking(inWaypoints[inWaypoints.length - 1]), + getDocking(outWaypoints[1]), + getDocking(outWaypoints[0])); + + if (intersection) { + return [].concat( + inWaypoints.slice(0, inWaypoints.length - 1), + [ intersection ], + outWaypoints.slice(1)); + } else { + return [ + getDocking(inWaypoints[0]), + getDocking(outWaypoints[outWaypoints.length - 1]) + ]; + } +} \ No newline at end of file diff --git a/lib/features/modeling/behavior/util/LabelLayoutUtil.js b/lib/features/modeling/behavior/util/LabelLayoutUtil.js index 44930025..e7cc8d73 100644 --- a/lib/features/modeling/behavior/util/LabelLayoutUtil.js +++ b/lib/features/modeling/behavior/util/LabelLayoutUtil.js @@ -2,7 +2,7 @@ var GeometricUtil = require('./GeometricUtil'); -var getDistance = require('./GeometricUtil').getDistancePointPoint; +var getDistancePointPoint = require('./GeometricUtil').getDistancePointPoint; var getAttachment = require('./LineAttachmentUtil').getAttachment; @@ -183,8 +183,8 @@ module.exports.getLabelAdjustment = getLabelAdjustment; function relativePositionMidWaypoint(waypoints, idx) { - var distanceSegment1 = getDistance(waypoints[idx-1], waypoints[idx]), - distanceSegment2 = getDistance(waypoints[idx], waypoints[idx+1]); + var distanceSegment1 = getDistancePointPoint(waypoints[idx-1], waypoints[idx]), + distanceSegment2 = getDistancePointPoint(waypoints[idx], waypoints[idx+1]); var relativePosition = distanceSegment1 / ( distanceSegment1 + distanceSegment2 ); @@ -210,7 +210,8 @@ function getLine(waypoints, idx) { } function getRelativeFootPosition(line, foot) { - var length = GeometricUtil.getDistancePointPoint(line[0], line[1]), - lengthToFoot = GeometricUtil.getDistancePointPoint(line[0], foot); + var length = getDistancePointPoint(line[0], line[1]), + lengthToFoot = getDistancePointPoint(line[0], foot); + return lengthToFoot / length; } diff --git a/lib/features/modeling/behavior/util/LineIntersect.js b/lib/features/modeling/behavior/util/LineIntersect.js new file mode 100644 index 00000000..47af27a9 --- /dev/null +++ b/lib/features/modeling/behavior/util/LineIntersect.js @@ -0,0 +1,37 @@ +'use strict'; + +/** + * Returns the intersection between two line segments a and b. + * + * @param {Point} l1s + * @param {Point} l1e + * @param {Point} l2s + * @param {Point} l2e + * + * @return {Point} + */ +module.exports = function lineIntersect(l1s, l1e, l2s, l2e) { + // if the lines intersect, the result contains the x and y of the + // intersection (treating the lines as infinite) and booleans for + // whether line segment 1 or line segment 2 contain the point + var denominator, a, b, c, numerator; + + denominator = ((l2e.y - l2s.y) * (l1e.x - l1s.x)) - ((l2e.x - l2s.x) * (l1e.y - l1s.y)); + + if (denominator == 0) { + return null; + } + + a = l1s.y - l2s.y; + b = l1s.x - l2s.x; + numerator = ((l2e.x - l2s.x) * a) - ((l2e.y - l2s.y) * b); + + c = numerator / denominator; + + // if we cast these lines infinitely in + // both directions, they intersect here + return { + x: Math.round(l1s.x + (c * (l1e.x - l1s.x))), + y: Math.round(l1s.y + (c * (l1e.y - l1s.y))) + }; +}; \ No newline at end of file diff --git a/test/spec/features/modeling/behavior/RemoveElementBehavior.bpmn b/test/spec/features/modeling/behavior/RemoveElementBehavior.bpmn index 6260c87f..c2d5ab62 100644 --- a/test/spec/features/modeling/behavior/RemoveElementBehavior.bpmn +++ b/test/spec/features/modeling/behavior/RemoveElementBehavior.bpmn @@ -1,5 +1,5 @@ - + SequenceFlow1 @@ -39,101 +39,142 @@ SequenceFlow5 + + SequenceFlow7 + + + SequenceFlow7 + SequenceFlow8 + + + + SequenceFlow8 + + - + - + - + - + - + - - + + - + - - + + - - + + - + - - - + + + - + - - + + - - - - - + + + + + - + - - + + - + - - - + + + - + - - + + - + - - - + + + - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/behavior/RemoveElementBehavior.perpendicular.bpmn b/test/spec/features/modeling/behavior/RemoveElementBehavior.perpendicular.bpmn new file mode 100644 index 00000000..086c4ece --- /dev/null +++ b/test/spec/features/modeling/behavior/RemoveElementBehavior.perpendicular.bpmn @@ -0,0 +1,159 @@ + + + + + SequenceFlow_1 + + + + SequenceFlow_1 + SequenceFlow_2 + + + SequenceFlow_7 + + + SequenceFlow_7 + SequenceFlow_8 + + + SequenceFlow_8 + + + + SequenceFlow_Diagonal + SequenceFlow_3 + + + SequenceFlow_3 + SequenceFlow_4 + + + + SequenceFlow_4 + + + + SequenceFlow_5 + + + SequenceFlow_5 + SequenceFlow_6 + + + + SequenceFlow_2 + SequenceFlow_Diagonal + + + + SequenceFlow_6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/behavior/RemoveElementBehaviorSpec.js b/test/spec/features/modeling/behavior/RemoveElementBehaviorSpec.js index b08fe005..f2d4476c 100644 --- a/test/spec/features/modeling/behavior/RemoveElementBehaviorSpec.js +++ b/test/spec/features/modeling/behavior/RemoveElementBehaviorSpec.js @@ -5,36 +5,198 @@ var modelingModule = require('../../../../../lib/features/modeling'), coreModule = require('../../../../../lib/core'); + describe('features/modeling - remove element behavior', function() { var testModules = [ coreModule, modelingModule ]; + describe('combine sequence flow when deleting element', function() { + + describe('parallel connections', function() { + + var processDiagramXML = require('./RemoveElementBehavior.bpmn'); + + beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules })); + + + it('horizontal', inject(function(modeling, elementRegistry) { + + // given + var task = elementRegistry.get('Task1'); + + // when + modeling.removeShape(task); + + // then + var sequenceFlow1 = elementRegistry.get('SequenceFlow1'); + var waypoints = sequenceFlow1.waypoints; + + // SequenceFlow2 should be deleted + expect(elementRegistry.get(task.id)).to.be.undefined; + expect(sequenceFlow1).to.not.be.undefined; + expect(elementRegistry.get('SequenceFlow2')).to.be.undefined; + + // source and target have one connection each + expect(elementRegistry.get('StartEvent1').outgoing.length).to.be.equal(1); + expect(elementRegistry.get('EndEvent1').incoming.length).to.be.equal(1); + + // connection has two horizontally equal waypoints + expect(waypoints).to.have.length(2); + expect(waypoints[0].y).to.eql(waypoints[1].y); + + })); + + + it('vertical', inject(function(modeling, elementRegistry) { + + // given + var task = elementRegistry.get('Task4'); + + // when + modeling.removeShape(task); + + // then + var waypoints = elementRegistry.get('SequenceFlow7').waypoints; + // connection has two vertically equal waypoints + expect(waypoints).to.have.length(2); + expect(waypoints[0].x).to.eql(waypoints[1].x); + + })); + + }); + + + describe('perpendicular connections', function() { + + var gatewayDiagramXML = require('./RemoveElementBehavior.perpendicular.bpmn'); + + beforeEach(bootstrapModeler(gatewayDiagramXML, { modules: testModules })); + + + it('right-down', inject(function(modeling, elementRegistry) { + + // given + var task = elementRegistry.get('Task_2'); + var mid = { + x : task.x + task.width / 2, + y : task.y + task.height / 2 + }; + + // when + modeling.removeShape(task); + + // then + var waypoints = elementRegistry.get('SequenceFlow_1').waypoints; + expect(waypoints).to.have.length(3); + + var intersec = waypoints[1]; + expect(intersec).to.eql(point(mid)); + + })); + + + it('right-up', inject(function(modeling, elementRegistry) { + + // given + var task = elementRegistry.get('Task_11'); + var mid = { + x : task.x + task.width / 2, + y : task.y + task.height / 2 + }; + + // when + modeling.removeShape(task); + + // then + var waypoints = elementRegistry.get('SequenceFlow_7').waypoints; + expect(waypoints).to.have.length(3); + + var intersec = waypoints[1]; + expect(intersec).to.eql(point(mid)); + + })); + + + it('down-right', inject(function(modeling, elementRegistry) { + + // given + var task = elementRegistry.get('Task_5'); + var mid = { + x : task.x + task.width / 2, + y : task.y + task.height / 2 + }; + + // when + modeling.removeShape(task); + + // then + var waypoints = elementRegistry.get('SequenceFlow_3').waypoints; + expect(waypoints).to.have.length(3); + + var intersec = waypoints[1]; + expect(intersec).to.eql(point(mid)); + + })); + + + it('up-right', inject(function(modeling, elementRegistry) { + + // given + var task = elementRegistry.get('Task_8'); + var mid = { + x : task.x + task.width / 2, + y : task.y + task.height / 2 + }; + + // when + modeling.removeShape(task); + + // then + var waypoints = elementRegistry.get('SequenceFlow_5').waypoints; + expect(waypoints).to.have.length(3); + + var intersec = waypoints[1]; + expect(intersec).to.eql(point(mid)); + + })); + + + it('diagonal', inject(function(modeling, elementRegistry) { + + // given + var task = elementRegistry.get('Task_4'); + var mid = { + x: task.x + task.width / 2, + y: 211 + }; + + // when + modeling.removeShape(task); + + // then + var waypoints = elementRegistry.get('SequenceFlow_Diagonal').waypoints; + expect(waypoints).to.have.length(3); + + var intersec = waypoints[1]; + expect(intersec).to.eql(point(mid)); + + })); + + }); + + }); + + + describe('do not combine sequence flows ', function() { + var processDiagramXML = require('./RemoveElementBehavior.bpmn'); beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules })); - it('should combine sequence flows on remove', inject(function(modeling, elementRegistry) { - // given - var task = elementRegistry.get('Task1'); - - // when - modeling.removeShape(task); - - // then - expect(elementRegistry.get(task.id)).to.be.undefined; - expect(elementRegistry.get('SequenceFlow1')).to.not.be.undefined; - expect(elementRegistry.get('SequenceFlow2')).to.be.undefined; - - expect(elementRegistry.get('StartEvent1').outgoing.length).to.be.equal(1); - expect(elementRegistry.get('EndEvent1').incoming.length).to.be.equal(1); - - })); - - - it('should remove all sequence flows', inject(function(modeling, elementRegistry) { + it('remove all if there are more than one incoming or outgoing', inject(function(modeling, elementRegistry) { // given var task = elementRegistry.get('Task3'); @@ -54,7 +216,7 @@ describe('features/modeling - remove element behavior', function() { })); - it('should not combine not allowed connection', inject(function(modeling, elementRegistry) { + it('when connection is not allowed', inject(function(modeling, elementRegistry) { // given var task = elementRegistry.get('Task2'); @@ -75,3 +237,15 @@ describe('features/modeling - remove element behavior', function() { }); }); + + + + +////////////////////////// helper ///////////////////////////////// + +function point(p) { + return { + x: p.x, + y: p.y + }; +} diff --git a/test/spec/features/modeling/behavior/util/LineIntersectSpec.js b/test/spec/features/modeling/behavior/util/LineIntersectSpec.js new file mode 100644 index 00000000..47589ed7 --- /dev/null +++ b/test/spec/features/modeling/behavior/util/LineIntersectSpec.js @@ -0,0 +1,33 @@ +'use strict'; + +require('../../../../../TestHelper'); + +var intersection = require('lib/features/modeling/behavior/util/LineIntersect'); + + +describe('modeling/behavior/util - LineIntersect', function() { + + it('should compute intersections', function() { + expect(intersection( + { x: 10, y: 20 }, { x: 50, y: 50 }, + { x: 10, y: 50 }, { x: 50, y: 50 } + )).to.eql({ x: 50, y: 50 }); + + expect(intersection( + { x: 10, y: 20 }, { x: 10, y: 50 }, + { x: 10, y: 50 }, { x: 50, y: 50 } + )).to.eql({ x: 10, y: 50 }); + + expect(intersection( + { x: 50, y: 50 }, { x: 10, y: 40 }, + { x: 10, y: 50 }, { x: 50, y: 50 } + )).to.eql({ x: 50, y: 50 }); + + expect(intersection( + { x: 0, y: 0 }, { x: 100, y: 100 }, + { x: 40, y: 0 }, { x: 30, y: 10 } + )).to.eql({ x: 20, y: 20 }); + + }); + +}); \ No newline at end of file