Skip to content

Commit

Permalink
refactor: extract waypoints code to dedicated classes (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
tbouffard authored Jun 4, 2021
1 parent 623a688 commit 0ae0add
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 185 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
*/
package io.process.analytics.tools.bpmn.generator.converter;

import java.util.ArrayList;
import java.util.List;

import io.process.analytics.tools.bpmn.generator.converter.waypoint.WayPointsComputer;
import io.process.analytics.tools.bpmn.generator.model.*;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -82,195 +82,20 @@ public DisplayModel convert(Grid grid, Diagram diagram) {
.rx(y(10)).strokeWidth(y(5)).build());
}

WayPointsComputer wayPointsComputer = new WayPointsComputer(grid, model.flowNodes);
diagram.getEdges()
.stream()
.map(edge -> new DisplayEdge(edge.getId(), inferWayPoints(edge, grid, model.flowNodes)))
.map(edge -> new DisplayEdge(edge.getId(), wayPointsComputer.inferWayPoints(edge)))
.forEach(model::edge);

return model.build();
}

private List<DisplayPoint> inferWayPoints(Edge edge, Grid grid, List<DisplayFlowNode> flowNodes) {
Position positionFrom = getPositionOfShape(grid, edge.getFrom());
Position positionTo = getPositionOfShape(grid, edge.getTo());

EdgeDirection edgeDirection = computeEdgeDirection(positionFrom, positionTo, grid);

DisplayFlowNode flowNodeFrom = getFlowNode(flowNodes, positionFrom.getShape());
DisplayFlowNode flowNodeTo = getFlowNode(flowNodes, positionTo.getShape());

return computeWayPoints(edgeDirection, flowNodeFrom, flowNodeTo);
}

private List<DisplayPoint> computeWayPoints(EdgeDirection edgeDirection, DisplayFlowNode flowNodeFrom, DisplayFlowNode flowNodeTo) {
DisplayDimension dimensionFrom = flowNodeFrom.dimension;
DisplayDimension dimensionTo = flowNodeTo.dimension;

List<DisplayPoint> wayPoints = new ArrayList<>();
switch (edgeDirection) {
case HorizontalLeftToRight:
wayPoints.add(new DisplayPoint(dimensionFrom.x + dimensionFrom.width, dimensionFrom.y + dimensionFrom.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x, dimensionTo.y + dimensionTo.height / 2));
break;
case HorizontalRightToLeft:
wayPoints.add(new DisplayPoint(dimensionFrom.x, dimensionFrom.y + dimensionFrom.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x + dimensionTo.width, dimensionTo.y + dimensionTo.height / 2));
break;
case BottomLeftToTopRight_FirstHorizontal: // horizontal then vertical
wayPoints.add(new DisplayPoint(dimensionFrom.x + dimensionFrom.width, dimensionFrom.y + dimensionFrom.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x + dimensionTo.width / 2, dimensionFrom.y + dimensionFrom.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x + dimensionTo.width / 2, dimensionTo.y + dimensionTo.height));
break;
case BottomLeftToTopRight_FirstVertical: // vertical then horizontal
wayPoints.add(new DisplayPoint(dimensionFrom.x + dimensionFrom.width / 2, dimensionFrom.y));
wayPoints.add(new DisplayPoint(dimensionFrom.x + dimensionFrom.width / 2, dimensionTo.y + dimensionTo.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x, dimensionTo.y + dimensionTo.height / 2));
break;
case BottomRightToTopLeft_FirstHorizontal: // horizontal then vertical
wayPoints.add(new DisplayPoint(dimensionFrom.x, dimensionFrom.y + dimensionFrom.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x + dimensionTo.width / 2,
dimensionFrom.y + dimensionFrom.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x + dimensionTo.width / 2, dimensionTo.y + dimensionTo.height));
break;
case BottomRightToTopLeft_FirstVertical: // vertical then horizontal
wayPoints.add(new DisplayPoint(dimensionFrom.x + dimensionFrom.width / 2, dimensionFrom.y));
wayPoints.add(new DisplayPoint(dimensionFrom.x + dimensionFrom.width / 2, dimensionTo.y + dimensionTo.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x + dimensionTo.width / 2, dimensionTo.y + dimensionTo.height / 2));
break;
case TopLeftToBottomRight_FirstHorizontal: // horizontal then vertical
wayPoints.add(new DisplayPoint(dimensionFrom.x + dimensionFrom.width, dimensionFrom.y + dimensionFrom.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x + dimensionTo.width / 2, dimensionFrom.y + dimensionFrom.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x + dimensionTo.width / 2, dimensionTo.y));
break;
case TopLeftToBottomRight_FirstVertical: // vertical then horizontal
wayPoints.add(new DisplayPoint(dimensionFrom.x + dimensionFrom.width / 2, dimensionFrom.y + dimensionTo.height));
wayPoints.add(new DisplayPoint(dimensionFrom.x + dimensionFrom.width / 2, dimensionTo.y + dimensionTo.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x, dimensionTo.y + dimensionTo.height / 2));
break;
case TopRightToBottomLeft_FirstHorizontal: // horizontal then vertical
wayPoints.add(new DisplayPoint(dimensionFrom.x, dimensionFrom.y + dimensionFrom.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x + dimensionTo.width / 2, dimensionFrom.y + dimensionFrom.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x + dimensionTo.width / 2, dimensionTo.y));
break;
case TopRightToBottomLeft_FirstVertical: // vertical then horizontal
wayPoints.add(new DisplayPoint(dimensionFrom.x + dimensionFrom.width / 2, dimensionFrom.y + dimensionTo.height));
wayPoints.add(new DisplayPoint(dimensionFrom.x + dimensionFrom.width / 2, dimensionTo.y + dimensionTo.height / 2));
wayPoints.add(new DisplayPoint(dimensionTo.x + dimensionTo.width / 2, dimensionTo.y + dimensionTo.height / 2));
break;
case VerticalBottomToTop:
wayPoints.add(new DisplayPoint(dimensionFrom.x + dimensionFrom.width / 2, dimensionFrom.y));
wayPoints.add(new DisplayPoint(dimensionTo.x + dimensionTo.width / 2, dimensionTo.y + dimensionTo.height));
break;
case VerticalTopToBottom:
wayPoints.add(new DisplayPoint(dimensionFrom.x + dimensionFrom.width / 2, dimensionFrom.y + dimensionFrom.height));
wayPoints.add(new DisplayPoint(dimensionTo.x + dimensionTo.width / 2, dimensionTo.y));
break;
default:
// do nothing
}
return wayPoints;
}

private DisplayFlowNode getFlowNode(List<DisplayFlowNode> flowNodes, String flowNodeId) {
return flowNodes.stream()
.filter(f -> flowNodeId.equals(f.bpmnElementId))
.findFirst()
.get(); // always exist, otherwise error occur on flow node generation
}

private Position getPositionOfShape(Grid grid, String shapeId) {
return grid.getPositions()
.stream()
.filter(p -> shapeId.equals(p.getShape()))
.findFirst()
.get(); // always exist, otherwise error occur on flow node generation
}

// visible for testing
static EdgeDirection computeEdgeDirection(Position positionFrom, Position positionTo, Grid grid) {
EdgeDirection edgeDirection = null;
if (positionFrom.getX() == positionTo.getX()) {
if (positionFrom.getY() < positionTo.getY()) {
edgeDirection = EdgeDirection.VerticalTopToBottom;
}
else {
edgeDirection = EdgeDirection.VerticalBottomToTop;
}
}
else if (positionFrom.getY() == positionTo.getY()) {
if (positionFrom.getX() < positionTo.getX()) {
edgeDirection = EdgeDirection.HorizontalLeftToRight;
}
else {
edgeDirection = EdgeDirection.HorizontalRightToLeft;
}
} else if (positionFrom.getX() < positionTo.getX()) {
if (positionFrom.getY() < positionTo.getY()) {
boolean shapeExistAtRightPositionFrom = grid.getPositions()
.stream()
.filter(p -> p.getY() == positionFrom.getY())
.anyMatch(p -> p.getX() == positionFrom.getX() + 1);
edgeDirection = shapeExistAtRightPositionFrom || (isGatewayAt(positionFrom) && (!isGatewayAt(positionTo) || isGatewaySplitAt(positionTo))) ? EdgeDirection.TopLeftToBottomRight_FirstVertical : EdgeDirection.TopLeftToBottomRight_FirstHorizontal;
}
else {
if (isGatewayAt(positionFrom)) {
edgeDirection = !isGatewaySplitAt(positionFrom) || isGatewayAt(positionTo) && !isGatewaySplitAt(positionTo) ? EdgeDirection.BottomLeftToTopRight_FirstHorizontal : EdgeDirection.BottomLeftToTopRight_FirstVertical;
} else {
boolean shapeExistAbovePositionFrom = grid.getPositions()
.stream()
.filter(p -> p.getX() == positionFrom.getX())
.anyMatch(p -> p.getY() == positionFrom.getY() - 1);
edgeDirection = shapeExistAbovePositionFrom || isGatewayAt(positionTo) ? EdgeDirection.BottomLeftToTopRight_FirstHorizontal : EdgeDirection.BottomLeftToTopRight_FirstVertical;
}
}
} else {
if (positionFrom.getY() < positionTo.getY()) {
boolean shapeExistAtLeftPositionFrom = grid.getPositions()
.stream()
.filter(p -> p.getY() == positionFrom.getY())
.anyMatch(p -> p.getX() == positionFrom.getX() - 1);
edgeDirection = shapeExistAtLeftPositionFrom ? EdgeDirection.TopRightToBottomLeft_FirstVertical : EdgeDirection.TopRightToBottomLeft_FirstHorizontal;
} else {
boolean shapeExistAbovePositionFrom = grid.getPositions()
.stream()
.filter(p -> p.getX() == positionFrom.getX())
.anyMatch(p -> p.getY() == positionFrom.getY() - 1);
edgeDirection = shapeExistAbovePositionFrom ? EdgeDirection.BottomRightToTopLeft_FirstHorizontal : EdgeDirection.BottomRightToTopLeft_FirstVertical;
}
}

return edgeDirection;
}

private static boolean isGatewayAt(Position position) {
return ShapeType.GATEWAY.equals(position.getShapeType());
}

private static boolean isGatewaySplitAt(Position position) {
return isGatewayAt(position) && position.isSplitGateway();
}

// visible for testing
static enum EdgeDirection {
HorizontalLeftToRight,
HorizontalRightToLeft,
VerticalBottomToTop,
VerticalTopToBottom,
BottomLeftToTopRight_FirstHorizontal,
BottomLeftToTopRight_FirstVertical,
BottomRightToTopLeft_FirstHorizontal,
BottomRightToTopLeft_FirstVertical,
TopLeftToBottomRight_FirstHorizontal,
TopLeftToBottomRight_FirstVertical,
TopRightToBottomLeft_FirstHorizontal,
TopRightToBottomLeft_FirstVertical,
}

private int x(int percentage) {
private static int x(int percentage) {
return CELL_WIDTH * percentage / 100;
}

private int y(int percentage) {
private static int y(int percentage) {
return CELL_HEIGHT * percentage / 100;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2021 Bonitasoft S.A.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.process.analytics.tools.bpmn.generator.converter.waypoint;

import io.process.analytics.tools.bpmn.generator.model.Grid;
import io.process.analytics.tools.bpmn.generator.model.Position;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;

import java.util.List;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Log4j2
public class GridSearcher {

private final Grid grid;

public Position getPositionOfShape(String shapeId) {
return grid.getPositions()
.stream()
.filter(p -> shapeId.equals(p.getShape()))
.findFirst()
.get(); // always exist, otherwise error occur on flow node generation
}

public boolean isShapeExistAtLeft(Position position) {
return grid.getPositions()
.stream()
.filter(p -> p.getY() == position.getY())
.anyMatch(p -> p.getX() == position.getX() - 1);
}

public boolean isShapeExistAtRight(final Position position) {
return grid.getPositions()
.stream()
.filter(p -> p.getY() == position.getY())
.anyMatch(p -> p.getX() == position.getX() + 1);
}

public boolean isShapeExistAbove(final Position positionFrom) {
return grid.getPositions()
.stream()
.filter(p -> p.getX() == positionFrom.getX())
.anyMatch(p -> p.getY() == positionFrom.getY() - 1);
}

}
Loading

0 comments on commit 0ae0add

Please sign in to comment.