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:
parent
4777b42f63
commit
144157f9eb
|
@ -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;
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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' ]);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue