use of org.eclipse.elk.core.math.KVector in project elk by eclipse.
the class LabelPlacer method placeHorizontalOuterNodeLabelContainer.
/**
* Places a horizontal outer node label container on the given side.
*/
private static void placeHorizontalOuterNodeLabelContainer(final NodeContext nodeContext, final boolean outerNodeLabelsOverhang, final PortSide portSide) {
KVector nodeSize = nodeContext.nodeSize;
StripContainerCell nodeLabelContainer = nodeContext.outsideNodeLabelContainers.get(portSide);
ElkRectangle nodeLabelContainerRect = nodeLabelContainer.getCellRectangle();
// Set the container's width and height to its minimum width and height
nodeLabelContainerRect.width = nodeLabelContainer.getMinimumWidth();
nodeLabelContainerRect.height = nodeLabelContainer.getMinimumHeight();
// The container must be at least as wide as the node is
nodeLabelContainerRect.width = Math.max(nodeLabelContainerRect.width, nodeSize.x);
// If node labels are not allowed to overhang and if they would do so right now, make the container smaller
if (nodeLabelContainerRect.width > nodeSize.x && !outerNodeLabelsOverhang) {
nodeLabelContainerRect.width = nodeSize.x;
}
// Container's x coordinate
nodeLabelContainerRect.x = -(nodeLabelContainerRect.width - nodeSize.x) / 2;
// Container's y coordinate depends on whether we place the thing on the northern or southern side
switch(portSide) {
case NORTH:
nodeLabelContainerRect.y = -nodeLabelContainerRect.height;
break;
case SOUTH:
nodeLabelContainerRect.y = nodeSize.y;
break;
}
// Layout the container's children
nodeLabelContainer.layoutChildrenHorizontally();
nodeLabelContainer.layoutChildrenVertically();
}
use of org.eclipse.elk.core.math.KVector in project elk by eclipse.
the class NodeSizeCalculator method setNodeHeight.
// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Node Height
/**
* Sets the node's height according to the active node size constraints. Also sets that height on the cell system
* and tells it to compute a horizontal layout.
*/
public static void setNodeHeight(final NodeContext nodeContext) {
KVector nodeSize = nodeContext.nodeSize;
double height;
if (NodeLabelAndSizeUtilities.areSizeConstraintsFixed(nodeContext)) {
// Simply use the node's current height
height = nodeSize.y;
} else {
// Ask the cell system how heigh it would like to be
height = nodeContext.nodeContainer.getMinimumHeight();
// If we include node labels and outside node labels are not to overhang, we need to include those as well
if (nodeContext.sizeConstraints.contains(SizeConstraint.NODE_LABELS) && !nodeContext.sizeOptions.contains(SizeOptions.OUTSIDE_NODE_LABELS_OVERHANG)) {
height = Math.max(height, nodeContext.outsideNodeLabelContainers.get(PortSide.EAST).getMinimumHeight());
height = Math.max(height, nodeContext.outsideNodeLabelContainers.get(PortSide.WEST).getMinimumHeight());
}
// The node might have a minimum size set...
KVector minNodeSize = NodeLabelAndSizeUtilities.getMinimumNodeSize(nodeContext);
if (minNodeSize != null) {
height = Math.max(height, minNodeSize.y);
}
// come out of the cell system
if (nodeContext.sizeConstraints.contains(SizeConstraint.PORTS)) {
if (nodeContext.portConstraints == PortConstraints.FIXED_RATIO || nodeContext.portConstraints == PortConstraints.FIXED_POS) {
height = Math.max(height, nodeContext.insidePortLabelCells.get(PortSide.EAST).getMinimumHeight());
height = Math.max(height, nodeContext.insidePortLabelCells.get(PortSide.WEST).getMinimumHeight());
}
}
}
// Set the node's height
nodeSize.y = height;
// Set the cell system's height and tell it to compute vertical coordinates and heights
ElkRectangle nodeCellRectangle = nodeContext.nodeContainer.getCellRectangle();
nodeCellRectangle.y = 0;
nodeCellRectangle.height = height;
nodeContext.nodeContainer.layoutChildrenVertically();
}
use of org.eclipse.elk.core.math.KVector in project elk by eclipse.
the class PortLabelPlacementCalculator method simpleOutsidePortLabelPlacement.
// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Simple Outside Port Labels
/**
* Place the port label cells outside of the node.
*/
private static void simpleOutsidePortLabelPlacement(final NodeContext nodeContext, final PortSide portSide) {
Collection<PortContext> portContexts = nodeContext.portContexts.get(portSide);
// If there are only two ports on a side, we place the first port's label on its other side to make it
// especially clear which port it belongs to. The same applies if the user requested space-efficient mode
boolean placeFirstPortDifferently = NodeLabelAndSizeUtilities.isFirstOutsidePortLabelPlacedDifferently(nodeContext, portSide);
for (PortContext portContext : portContexts) {
// If the port doesn't have labels, skip
if (portContext.portLabelCell == null || !portContext.portLabelCell.hasLabels()) {
continue;
}
// Retrieve information about the port itself
KVector portSize = portContext.port.getSize();
// Retrieve the label cell and its rectangle and set the rectangle's size (we will use the rectangle to
// place the cell relative to the port below)
LabelCell portLabelCell = portContext.portLabelCell;
ElkRectangle portLabelCellRect = portLabelCell.getCellRectangle();
portLabelCellRect.width = portLabelCell.getMinimumWidth();
portLabelCellRect.height = portLabelCell.getMinimumHeight();
// Calculate the position of the port's label space
switch(portSide) {
case NORTH:
if (portContext.labelsNextToPort) {
portLabelCellRect.x = (portSize.x - portLabelCellRect.width) / 2;
portLabelCell.setHorizontalAlignment(HorizontalLabelAlignment.CENTER);
} else if (placeFirstPortDifferently) {
portLabelCellRect.x = -portLabelCellRect.width - nodeContext.portLabelSpacing;
portLabelCell.setHorizontalAlignment(HorizontalLabelAlignment.RIGHT);
} else {
portLabelCellRect.x = portSize.x + nodeContext.portLabelSpacing;
portLabelCell.setHorizontalAlignment(HorizontalLabelAlignment.LEFT);
}
portLabelCellRect.y = -portLabelCellRect.height - nodeContext.portLabelSpacing;
portLabelCell.setVerticalAlignment(VerticalLabelAlignment.BOTTOM);
break;
case SOUTH:
if (portContext.labelsNextToPort) {
portLabelCellRect.x = (portSize.x - portLabelCellRect.width) / 2;
portLabelCell.setHorizontalAlignment(HorizontalLabelAlignment.CENTER);
} else if (placeFirstPortDifferently) {
portLabelCellRect.x = -portLabelCellRect.width - nodeContext.portLabelSpacing;
portLabelCell.setHorizontalAlignment(HorizontalLabelAlignment.RIGHT);
} else {
portLabelCellRect.x = portSize.x + nodeContext.portLabelSpacing;
portLabelCell.setHorizontalAlignment(HorizontalLabelAlignment.LEFT);
}
portLabelCellRect.y = portSize.y + nodeContext.portLabelSpacing;
portLabelCell.setVerticalAlignment(VerticalLabelAlignment.TOP);
break;
case EAST:
if (portContext.labelsNextToPort) {
double labelHeight = nodeContext.portLabelsTreatAsGroup ? portLabelCellRect.height : portLabelCell.getLabels().get(0).getSize().y;
portLabelCellRect.y = (portSize.y - labelHeight) / 2;
portLabelCell.setVerticalAlignment(VerticalLabelAlignment.CENTER);
} else if (placeFirstPortDifferently) {
portLabelCellRect.y = -portLabelCellRect.height - nodeContext.portLabelSpacing;
portLabelCell.setVerticalAlignment(VerticalLabelAlignment.BOTTOM);
} else {
portLabelCellRect.y = portSize.y + nodeContext.portLabelSpacing;
portLabelCell.setVerticalAlignment(VerticalLabelAlignment.TOP);
}
portLabelCellRect.x = portSize.x + nodeContext.portLabelSpacing;
portLabelCell.setHorizontalAlignment(HorizontalLabelAlignment.LEFT);
break;
case WEST:
if (portContext.labelsNextToPort) {
double labelHeight = nodeContext.portLabelsTreatAsGroup ? portLabelCellRect.height : portLabelCell.getLabels().get(0).getSize().y;
portLabelCellRect.y = (portSize.y - labelHeight) / 2;
portLabelCell.setVerticalAlignment(VerticalLabelAlignment.CENTER);
} else if (placeFirstPortDifferently) {
portLabelCellRect.y = -portLabelCellRect.height - nodeContext.portLabelSpacing;
portLabelCell.setVerticalAlignment(VerticalLabelAlignment.BOTTOM);
} else {
portLabelCellRect.y = portSize.y + nodeContext.portLabelSpacing;
portLabelCell.setVerticalAlignment(VerticalLabelAlignment.TOP);
}
portLabelCellRect.x = -portLabelCellRect.width - nodeContext.portLabelSpacing;
portLabelCell.setHorizontalAlignment(HorizontalLabelAlignment.RIGHT);
break;
}
// The next port definitely doesn't have special needs anymore
placeFirstPortDifferently = false;
}
}
use of org.eclipse.elk.core.math.KVector in project elk by eclipse.
the class NodeMarginCalculator method processNode.
/**
* Calculates the margin of the given node.
*
* @param node the node whose margin to calculate.
* @param labelSpacing label spacing set on the layered graph.
*/
private void processNode(final NodeAdapter<?> node, final double labelSpacing) {
// This will be our bounding box. We'll start with one that's the same size
// as our node, and at the same position.
ElkRectangle boundingBox = new ElkRectangle(node.getPosition().x, node.getPosition().y, node.getSize().x, node.getSize().y);
// We'll reuse this rectangle as our box for elements to add to the bounding box
ElkRectangle elementBox = new ElkRectangle();
// Put the node's labels into the bounding box
if (includeLabels) {
for (LabelAdapter<?> label : node.getLabels()) {
elementBox.x = label.getPosition().x + node.getPosition().x;
elementBox.y = label.getPosition().y + node.getPosition().y;
elementBox.width = label.getSize().x;
elementBox.height = label.getSize().y;
boundingBox.union(elementBox);
}
}
// Do the same for ports and their labels
for (PortAdapter<?> port : node.getPorts()) {
// Calculate the port's upper left corner's x and y coordinate
double portX = port.getPosition().x + node.getPosition().x;
double portY = port.getPosition().y + node.getPosition().y;
// The port itself
if (includePorts) {
elementBox.x = portX;
elementBox.y = portY;
elementBox.width = port.getSize().x;
elementBox.height = port.getSize().y;
boundingBox.union(elementBox);
}
// The port's labels
if (includePortLabels) {
for (LabelAdapter<?> label : port.getLabels()) {
elementBox.x = label.getPosition().x + portX;
elementBox.y = label.getPosition().y + portY;
elementBox.width = label.getSize().x;
elementBox.height = label.getSize().y;
boundingBox.union(elementBox);
}
}
// End labels of edges connected to the port
if (includeEdgeHeadTailLabels) {
KVector requiredPortLabelSpace = new KVector(-labelSpacing, -labelSpacing);
// TODO: maybe leave space for manually placed ports
if (node.getProperty(CoreOptions.PORT_LABELS_PLACEMENT).contains(PortLabelPlacement.OUTSIDE)) {
for (LabelAdapter<?> label : port.getLabels()) {
requiredPortLabelSpace.x += label.getSize().x + labelSpacing;
requiredPortLabelSpace.y += label.getSize().y + labelSpacing;
}
}
requiredPortLabelSpace.x = Math.max(requiredPortLabelSpace.x, 0.0);
requiredPortLabelSpace.y = Math.max(requiredPortLabelSpace.y, 0.0);
processEdgeHeadTailLabels(boundingBox, port.getOutgoingEdges(), port.getIncomingEdges(), node, port, requiredPortLabelSpace, labelSpacing);
}
}
// Process end labels of edges directly connected to the node
if (includeEdgeHeadTailLabels) {
processEdgeHeadTailLabels(boundingBox, node.getOutgoingEdges(), node.getIncomingEdges(), node, null, null, labelSpacing);
}
// Reset the margin (guard against very small double precision errors which can cause the results to be small
// negative values, which doesn't make sense -- see #616)
ElkMargin margin = new ElkMargin(node.getMargin());
margin.top = Math.max(0, node.getPosition().y - boundingBox.y);
margin.bottom = Math.max(0, boundingBox.y + boundingBox.height - (node.getPosition().y + node.getSize().y));
margin.left = Math.max(0, node.getPosition().x - boundingBox.x);
margin.right = Math.max(0, boundingBox.x + boundingBox.width - (node.getPosition().x + node.getSize().x));
node.setMargin(margin);
}
use of org.eclipse.elk.core.math.KVector in project elk by eclipse.
the class BowyerWatsonTriangulation method triangulate.
/**
* Triangulates a list of points.
*
* @param vertices the input points
* @param debugOutputFile file name for debug SVG. Debug output will be deactivated if this is null.
* @return the edges of the triangulation
*/
public static Set<TEdge> triangulate(final List<KVector> vertices, final String debugOutputFile) {
/*d*/
SVGImage svg = new SVGImage(debugOutputFile);
/*d*/
svg.addGroups("invalid", "tri", "bndry", "done", "new");
/* preliminaries */
// determine the bounding box of the given points
KVector topleft = new KVector(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
KVector bottomright = new KVector(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
for (KVector v : vertices) {
topleft.x = Math.min(topleft.x, v.x);
topleft.y = Math.min(topleft.y, v.y);
bottomright.x = Math.max(bottomright.x, v.x);
bottomright.y = Math.max(bottomright.y, v.y);
// CHECKSTYLEOFF MagicNumber
/*d*/
svg.g("bb").addCircle(v.x, v.y, 18, "stroke=\"black\" stroke-width=\"1\" fill=\"lightgray\"");
}
KVector size = new KVector(bottomright.x - topleft.x, bottomright.y - topleft.y);
/*d*/
svg.g("bb").addRect(topleft.x, topleft.y, size.x, size.y, "stroke=\"blue\" stroke-width=\"4\" fill=\"none\"");
// find a super-triangle spanning over all vertices
final double wiggleroom = 50;
// This ensures that no vertices are too close to the edges of the super-triangle
// because that would result in circumcircles that are so large that a double would not
// be precise enough to compute the circumcircle criterion.
KVector sa = new KVector(topleft.x - wiggleroom, topleft.y - size.x - wiggleroom);
KVector sb = new KVector(topleft.x - wiggleroom, bottomright.y + size.x + wiggleroom);
KVector sc = new KVector(bottomright.x + size.y / 2 + wiggleroom, topleft.y + size.y / 2);
/*d*/
svg.g("bb").addPoly("stroke=\"gray\" stroke-width=\"4\" fill=\"none\" stroke-dasharray=\"20,20\"", sa, sb, sc, sa);
TTriangle superTriangle = new TTriangle(sa, sb, sc);
/*d*/
// circumcircles will make it gigantic
svg.setViewBox(sa.x, sa.y, sc.x - sa.x, sb.y - sa.y);
/*d*/
svg.isave();
/*d*/
svg.removeGroup("bb");
/* Bowyer Watson algorithm*/
Set<TTriangle> triangulation = Sets.newHashSet();
List<TTriangle> invalidTriangles = Lists.newArrayList();
List<TEdge> boundary = Lists.newArrayList();
triangulation.add(superTriangle);
// incrementally adding vertices
for (KVector vertex : vertices) {
/*d*/
svg.g("done").addCircle(vertex.x, vertex.y, 18, "stroke=\"black\" stroke-width=\"1\" fill=\"lightgray\"");
/*d*/
svg.g("new").addCircle(vertex.x, vertex.y, 18, "stroke=\"black\" stroke-width=\"1\" fill=\"black\"");
// gather invalid triangles where the new vertex lies inside the circumcircle
invalidTriangles.clear();
for (TTriangle triangle : triangulation) {
/*d*/
svg.g("tri").addPoly("stroke=\"black\" fill=\"none\" stroke-width=\"4\"", triangle.a, triangle.b, triangle.c, triangle.a);
/*d*/
KVector c = triangle.getCircumcenter();
/*d*/
svg.g("invalid").addCircle(c.x, c.y, c.distance(triangle.a), "stroke=\"orange\" stroke-width=\"4\" fill=\"none\"");
if (triangle.inCircumcircle(vertex)) {
invalidTriangles.add(triangle);
/*d*/
svg.g("invalid").addPoly("stroke=\"none\" fill=\"red\" opacity=\"0.18\"", triangle.a, triangle.b, triangle.c, triangle.a);
}
}
/*d*/
svg.isave();
/*d*/
svg.clearGroup("invalid");
// calculate boundary of invalid triangles
boundary.clear();
for (TTriangle triangle : invalidTriangles) {
for (TEdge tEdge : triangle.tEdges) {
boolean onBoundary = true;
// edges that are not shared with other invalid triangles are on the boundary
for (TTriangle other : invalidTriangles) {
if (other != triangle && other.contains(tEdge)) {
onBoundary = false;
}
}
if (onBoundary) {
boundary.add(tEdge);
/*d*/
svg.g("bndry").addLine(tEdge.u.x, tEdge.u.y, tEdge.v.x, tEdge.v.y, "stroke=\"purple\" stroke-width=\"18\" stroke-dasharray=\"20,20\"");
}
}
}
/*d*/
svg.isave();
// remove invalid triangles
triangulation.removeAll(invalidTriangles);
/*d*/
svg.clearGroup("tri");
/*d*/
triangulation.forEach(triangle -> svg.g("tri").addPoly("stroke=\"black\" fill=\"none\" stroke-width=\"4\"", triangle.a, triangle.b, triangle.c, triangle.a));
/*d*/
svg.isave();
// triangulate boundary
for (TEdge tEdge : boundary) {
triangulation.add(new TTriangle(vertex, tEdge.u, tEdge.v));
/*d*/
svg.g("tri").addPoly("stroke=\"black\" fill=\"none\" stroke-width=\"4\"", vertex, tEdge.u, tEdge.v, vertex);
}
/*d*/
svg.isave();
/*d*/
svg.clearGroup("new");
/*d*/
svg.clearGroup("bndry");
/*d*/
svg.clearGroup("tri");
}
// convert triangulation to set of edges
// Because it's a set, edges that were redundant in the triangulation exist only once in the set.
Set<TEdge> tEdges = Sets.newHashSet();
triangulation.forEach(triangle -> tEdges.addAll(triangle.tEdges));
// remove edges connected to super triangle
Iterator<TEdge> i = tEdges.iterator();
while (i.hasNext()) {
TEdge tEdge = i.next();
if (superTriangle.contains(tEdge.u) || superTriangle.contains(tEdge.v)) {
i.remove();
}
}
/*d*/
tEdges.forEach(tEdge -> svg.addLine(tEdge.u.x, tEdge.u.y, tEdge.v.x, tEdge.v.y, "stroke=\"black\" stroke-width=\"4\""));
/*d*/
svg.isave();
return tEdges;
}
Aggregations