feat(label-behavior): move external label after resizing

* Generate reference point for external label 
* Adjust label position after resizing due to reference point delta

Closes #1051
This commit is contained in:
Niklas Kiefer 2019-06-11 11:13:33 +02:00 committed by merge-me[bot]
parent 4777b42f63
commit 144157f9eb
3 changed files with 456 additions and 2 deletions

View File

@ -12,6 +12,7 @@ import {
import {
isLabelExternal,
getExternalLabelMid,
hasExternalLabel
} from '../../../util/LabelUtil';
import {
@ -20,6 +21,28 @@ import {
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getNewAttachPoint
} from 'diagram-js/lib/util/AttachUtil';
import {
getMid,
roundPoint
} from 'diagram-js/lib/layout/LayoutUtil';
import {
delta
} from 'diagram-js/lib/util/PositionUtil';
import {
sortBy
} from 'min-dash';
import {
getDistancePointLine,
perpendicularFoot
} from './util/GeometricUtil';
var DEFAULT_LABEL_DIMENSIONS = {
width: 90,
height: 20
@ -207,6 +230,32 @@ export default function LabelBehavior(
}
});
// move external label after resizing
this.postExecute('shape.resize', function(event) {
var context = event.context,
shape = context.shape,
newBounds = context.newBounds,
oldBounds = context.oldBounds;
if (hasExternalLabel(shape)) {
var label = shape.label,
labelMid = getMid(label),
edges = asEdges(oldBounds);
// get nearest border point to label as reference point
var referencePoint = getReferencePoint(labelMid, edges);
var delta = getReferencePointDelta(referencePoint, oldBounds, newBounds);
modeling.moveShape(label, delta);
}
});
}
inherits(LabelBehavior, CommandInterceptor);
@ -216,4 +265,116 @@ LabelBehavior.$inject = [
'modeling',
'bpmnFactory',
'textRenderer'
];
];
// helpers //////////////////////
/**
* Calculates a reference point delta relative to a new position
* of a certain element's bounds
*
* @param {Point} point
* @param {Bounds} oldBounds
* @param {Bounds} newBounds
*
* @return {Delta} delta
*/
export function getReferencePointDelta(referencePoint, oldBounds, newBounds) {
var newReferencePoint = getNewAttachPoint(referencePoint, oldBounds, newBounds);
return roundPoint(delta(newReferencePoint, referencePoint));
}
/**
* Generates the nearest point (reference point) for a given point
* onto given set of lines
*
* @param {Array<Point, Point>} lines
* @param {Point} point
*
* @param {Point}
*/
export function getReferencePoint(point, lines) {
if (!lines.length) {
return;
}
var nearestLine = getNearestLine(point, lines);
return perpendicularFoot(point, nearestLine);
}
/**
* Convert the given bounds to a lines array containing all edges
*
* @param {Bounds|Point} bounds
*
* @return Array<Point>
*/
export function asEdges(bounds) {
return [
[ // top
{
x: bounds.x,
y: bounds.y
},
{
x: bounds.x + (bounds.width || 0),
y: bounds.y
}
],
[ // right
{
x: bounds.x + (bounds.width || 0),
y: bounds.y
},
{
x: bounds.x + (bounds.width || 0),
y: bounds.y + (bounds.height || 0)
}
],
[ // bottom
{
x: bounds.x,
y: bounds.y + (bounds.height || 0)
},
{
x: bounds.x + (bounds.width || 0),
y: bounds.y + (bounds.height || 0)
}
],
[ // left
{
x: bounds.x,
y: bounds.y
},
{
x: bounds.x,
y: bounds.y + (bounds.height || 0)
}
]
];
}
/**
* Returns the nearest line for a given point by distance
* @param {Point} point
* @param Array<Point> lines
*
* @return Array<Point>
*/
function getNearestLine(point, lines) {
var distances = lines.map(function(l) {
return {
line: l,
distance: getDistancePointLine(point, l)
};
});
var sorted = sortBy(distances, 'distance');
return sorted[0].line;
}

View File

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="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" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="1.16.0-dev">
<bpmn:definitions xmlns:bpmn="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" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.2.0-dev">
<bpmn:category id="Category_1">
<bpmn:categoryValue id="CategoryValue_1" value="Value" />
</bpmn:category>
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:startEvent id="StartEvent_1" name="foo" />
<bpmn:task id="Task_1" />
@ -8,6 +11,11 @@
<bpmn:text>foo</bpmn:text>
</bpmn:textAnnotation>
<bpmn:association id="Association_1w715hc" sourceRef="Task_1" targetRef="TextAnnotation_1" />
<bpmn:group id="Group_1" categoryValueRef="CategoryValue_1" />
<bpmn:group id="Group_2" categoryValueRef="CategoryValue_1" />
<bpmn:group id="Group_3" categoryValueRef="CategoryValue_1" />
<bpmn:group id="Group_4" categoryValueRef="CategoryValue_1" />
<bpmn:group id="Group_5" categoryValueRef="CategoryValue_1" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
@ -30,6 +38,36 @@
<di:waypoint x="441" y="80" />
<di:waypoint x="489" y="30" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Group_1_di" bpmnElement="Group_1">
<dc:Bounds x="162" y="296" width="128" height="110" />
<bpmndi:BPMNLabel>
<dc:Bounds x="300" y="303" width="28" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Group_2_di" bpmnElement="Group_2">
<dc:Bounds x="376" y="296" width="127" height="110" />
<bpmndi:BPMNLabel>
<dc:Bounds x="426" y="423" width="28" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Group_3_di" bpmnElement="Group_3">
<dc:Bounds x="565" y="296" width="130" height="110" />
<bpmndi:BPMNLabel>
<dc:Bounds x="526" y="303" width="28" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Group_4_di" bpmnElement="Group_4">
<dc:Bounds x="161" y="496" width="130" height="110" />
<bpmndi:BPMNLabel>
<dc:Bounds x="212" y="513" width="28" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Group_5_di" bpmnElement="Group_5">
<dc:Bounds x="352" y="496" width="130" height="110" />
<bpmndi:BPMNLabel>
<dc:Bounds x="403" y="583" width="28" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -5,6 +5,14 @@ import {
inject
} from 'test/TestHelper';
import {
resizeBounds
} from 'diagram-js/lib/features/resize/ResizeUtil';
import {
pick
} from 'min-dash';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
@ -370,4 +378,251 @@ describe('behavior - LabelBehavior', function() {
});
describe('resize label target', function() {
describe('should move label (outside)', function() {
it('to the top', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_2'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'se', {
x: 0,
y: -50
})
);
// then
expect(label.x).to.equal(labelBounds.x);
expect(label.y).to.be.below(labelBounds.y);
}));
it('to the right', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_1'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'se', {
x: 50,
y: 0
})
);
// then
expect(label.x).to.be.above(labelBounds.x);
expect(label.y).to.equal(labelBounds.y);
}));
it('to the bottom', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_2'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'se', {
x: 0,
y: 50
})
);
// then
expect(label.x).to.equal(labelBounds.x);
expect(label.y).to.be.above(labelBounds.y);
}));
it('to the left', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_3'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'sw', {
x: -50,
y: 0
})
);
// then
expect(label.x).to.be.below(labelBounds.x);
expect(label.y).to.equal(labelBounds.y);
}));
it('NOT if reference point not affected', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_2'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'ne', {
x: 0,
y: -50
})
);
// then
expect(getBounds(label)).to.eql(labelBounds);
}));
});
describe('should move label (inside)', function() {
it('to the top', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_4'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'nw', {
x: 0,
y: -50
})
);
// then
expect(label.x).to.equal(labelBounds.x);
expect(label.y).to.be.below(labelBounds.y);
}));
it('to the right', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_4'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'ne', {
x: 50,
y: 0
})
);
// then
expect(label.x).to.be.above(labelBounds.x);
expect(label.y).to.equal(labelBounds.y);
}));
it('to the bottom', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_5'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'sw', {
x: 0,
y: 50
})
);
// then
expect(label.x).to.equal(labelBounds.x);
expect(label.y).to.be.above(labelBounds.y);
}));
it('to the left', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_4'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'sw', {
x: -50,
y: 0
})
);
// then
expect(label.x).to.be.below(labelBounds.x);
expect(label.y).to.equal(labelBounds.y);
}));
it('NOT if reference point not affected', inject(function(elementRegistry, modeling) {
// given
var groupShape = elementRegistry.get('Group_4'),
label = groupShape.label,
labelBounds = getBounds(label);
// when
modeling.resizeShape(
groupShape,
resizeBounds(groupShape, 'se', {
x: 0,
y: 50
})
);
// then
expect(getBounds(label)).to.eql(labelBounds);
}));
});
});
});
// helper //////////
function getBounds(element) {
return pick(element, [ 'x', 'y', 'width', 'height' ]);
}