package org.simantics.diagram.elements; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.simantics.Simantics; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Statement; import org.simantics.db.UndoContext; import org.simantics.db.WriteGraph; import org.simantics.db.common.CommentMetadata; import org.simantics.db.common.request.IndexRoot; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.ManyObjectsForFunctionalRelationException; import org.simantics.db.exception.NoSingleResultException; import org.simantics.db.exception.ServiceException; import org.simantics.db.request.Write; import org.simantics.diagram.content.TerminalMap; import org.simantics.diagram.query.DiagramRequests; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.diagram.synchronization.graph.BasicResources; import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; import org.simantics.g2d.diagram.DiagramClass; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.impl.Diagram; import org.simantics.g2d.element.ElementClass; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.impl.Element; import org.simantics.g2d.utils.GeometryUtils; import org.simantics.scl.commands.Command; import org.simantics.scl.commands.Commands; import org.simantics.structural2.queries.Terminal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import gnu.trove.set.hash.THashSet; /** * Tools to align, rotate, and flip diagram elements. * * TODO : We need to add capability hints to elements to prevent rotating and mirroring elements that do not support that. * Example: mirrored text does not make any sense. * * * @author Marko Luukkainen (implementation) * @author Tuukka Lehtonen (documentation) */ public final class ElementTransforms { private static final Logger LOGGER = LoggerFactory.getLogger(ElementTransforms.class); public static enum SIDE { LEFT, RIGHT, TOP, BOTTOM, VERT, HORIZ, VERT_BTW, HORIZ_BTW }; /** * Align the specified set of diagram element resources in line with each * other calculated by the specified side. * *

* Alignment requires at least two elements to do anything. * * @param resources diagram element resources to rotate * @param side the side of each element to use for distancing. Does not support between aligments. */ public static void align(final Resource resources[], final SIDE side) { if (resources.length < 2) return; if (side == SIDE.HORIZ_BTW || side == SIDE.VERT_BTW ) return; Simantics.getSession().asyncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { graph.markUndoPoint(); IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT); List elements = new ArrayList(); for (Resource r : resources) { AlignElement e = create(graph, hints, r); if (e != null) elements.add(e); } if (elements.size() < 2) return; double mx = 0; double my = 0; for (AlignElement e : elements) { mx += e.transform[4]; my += e.transform[5]; } mx /= elements.size(); my /= elements.size(); // prevent moving symbols into the same position int count = 0; for (AlignElement e : elements) { if (side == SIDE.VERT || side == SIDE.LEFT || side == SIDE.RIGHT) { if (Math.abs(e.transform[5] - my) < 0.1) { count++; } } else { if (Math.abs(e.transform[4] - mx) < 0.1) { count++; } } } if (count > 1) return; if (side == SIDE.HORIZ || side == SIDE.VERT) { for (AlignElement e : elements) { if (side == SIDE.VERT) DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]}); else if (side == SIDE.HORIZ) { DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my}); } } } else { double lx, rx; double ty, by; lx = elements.get(0).transform[4] + elements.get(0).rotatedBounds.getMinX(); rx = elements.get(0).transform[4] + elements.get(0).rotatedBounds.getMaxX(); ty = elements.get(0).transform[5] + elements.get(0).rotatedBounds.getMinY(); by = elements.get(0).transform[5] + elements.get(0).rotatedBounds.getMaxY(); for (int i = 1; i < elements.size(); i++) { double tlx, trx; double tty, tby; tlx = elements.get(i).transform[4] + elements.get(i).rotatedBounds.getMinX(); trx = elements.get(i).transform[4] + elements.get(i).rotatedBounds.getMaxX(); tty = elements.get(i).transform[5] + elements.get(i).rotatedBounds.getMinY(); tby = elements.get(i).transform[5] + elements.get(i).rotatedBounds.getMaxY(); if (tlx < lx) lx = tlx; if (trx > rx) rx = trx; if (tty < ty) ty = tty; if (tby > by) by = tby; } for (AlignElement e : elements) { mx = e.transform[4]; my = e.transform[5]; if (side == SIDE.LEFT) { mx = lx - e.rotatedBounds.getMinX() ; } else if (side == SIDE.RIGHT) { mx = rx - e.rotatedBounds.getMaxX(); } else if (side == SIDE.TOP) { my = ty - e.rotatedBounds.getMinY(); } else { my = by - e.rotatedBounds.getMaxY(); } DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,my}); } } } }); } /** * Distance specified set of diagram element resources equally. Distancing * is performed on the specified side of the each element. * *

* Distancing requires at least three elements to work. * * @param resources diagram element resources to rotate * @param side the side of each element to use for distancing */ public static void dist(final Resource resources[], final SIDE side) { if (resources.length < 3) return; Simantics.getSession().asyncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { graph.markUndoPoint(); IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT); List elements = new ArrayList(); for (Resource r : resources) { //System.out.println(r + " " + GraphUtils.getReadableName(graph, r)); AlignElement e = create(graph, hints, r); if (e != null) elements.add(e); } if (elements.size() < 3) return; switch (side) { case LEFT: { Collections.sort(elements, new XComparator()); AlignElement left = elements.get(0); AlignElement right = elements.get(elements.size() - 1); double leftEdge = left.transform[4] + left.rotatedBounds.getMinX(); double rightEdge = right.transform[4] + right.rotatedBounds.getMinX(); double totalDist = rightEdge - leftEdge; double dist = totalDist / (elements.size() - 1); double d = leftEdge; for (int i = 1; i < elements.size() -1; i++) { d += dist; AlignElement e = elements.get(i); double mx = d - e.rotatedBounds.getMinX(); DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]}); } break; } case VERT: { Collections.sort(elements, new XComparator()); AlignElement left = elements.get(0); AlignElement right = elements.get(elements.size() - 1); double leftEdge = left.transform[4]; double rightEdge = right.transform[4]; double totalDist = rightEdge - leftEdge; double dist = totalDist / (elements.size() - 1); double d = leftEdge; for (int i = 1; i < elements.size() -1; i++) { d += dist; AlignElement e = elements.get(i); double mx = d; DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]}); } break; } case RIGHT:{ Collections.sort(elements, new XComparator()); AlignElement left = elements.get(0); AlignElement right = elements.get(elements.size() - 1); double leftEdge = left.transform[4] + left.rotatedBounds.getMaxX(); double rightEdge = right.transform[4] + right.rotatedBounds.getMaxX(); double totalDist = rightEdge - leftEdge; double dist = totalDist / (elements.size() - 1); double d = leftEdge; for (int i = 1; i < elements.size() -1; i++) { d += dist; AlignElement e = elements.get(i); double mx = d - e.rotatedBounds.getMaxX(); DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]}); } break; } case VERT_BTW:{ Collections.sort(elements, new XComparator()); AlignElement left = elements.get(0); AlignElement right = elements.get(elements.size() - 1); double leftEdge = left.transform[4] + left.rotatedBounds.getMaxX(); double rightEdge = right.transform[4] + right.rotatedBounds.getMinX(); double totalDist = rightEdge - leftEdge; double totalElementSize = 0; for (int i = 1; i < elements.size() -1; i++) { totalElementSize += elements.get(i).rotatedBounds.getWidth(); } double totalAvail = totalDist - totalElementSize; double dist = totalAvail / (elements.size() - 1); double d = leftEdge; for (int i = 1; i < elements.size() -1; i++) { d += dist; AlignElement e = elements.get(i); double mx = d - e.rotatedBounds.getMinX(); d += e.bounds.getWidth(); DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],mx,e.transform[5]}); } break; } case BOTTOM: { Collections.sort(elements, new YComparator()); AlignElement top = elements.get(0); AlignElement bottom = elements.get(elements.size() - 1); double topEdge = top.transform[5] + top.rotatedBounds.getMaxY(); double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMaxY(); double totalDist = bottomEdge - topEdge; double dist = totalDist / (elements.size() - 1); double d = topEdge; for (int i = 1; i < elements.size() -1; i++) { d += dist; AlignElement e = elements.get(i); double my = d - e.rotatedBounds.getMaxY(); DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my}); } break; } case TOP: { Collections.sort(elements, new YComparator()); AlignElement top = elements.get(0); AlignElement bottom = elements.get(elements.size() - 1); double topEdge = top.transform[5] + top.rotatedBounds.getMinY(); double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMinY(); double totalDist = bottomEdge - topEdge; double dist = totalDist / (elements.size() - 1); double d = topEdge; for (int i = 1; i < elements.size() -1; i++) { d += dist; AlignElement e = elements.get(i); double my = d - e.rotatedBounds.getMinY(); DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my}); } break; } case HORIZ: { Collections.sort(elements, new YComparator()); AlignElement top = elements.get(0); AlignElement bottom = elements.get(elements.size() - 1); double topEdge = top.transform[5]; double bottomEdge = bottom.transform[5]; double totalDist = bottomEdge - topEdge; double dist = totalDist / (elements.size() - 1); double d = topEdge; for (int i = 1; i < elements.size() -1; i++) { d += dist; AlignElement e = elements.get(i); double my = d; DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my}); } break; } case HORIZ_BTW: { Collections.sort(elements, new YComparator()); AlignElement top = elements.get(0); AlignElement bottom = elements.get(elements.size() - 1); double topEdge = top.transform[5] + top.rotatedBounds.getMaxY(); double bottomEdge = bottom.transform[5] + bottom.rotatedBounds.getMinY(); double totalDist = bottomEdge - topEdge; double totalElementSize = 0; for (int i = 1; i < elements.size() -1; i++) { totalElementSize += elements.get(i).rotatedBounds.getHeight(); } double totalAvail = totalDist - totalElementSize; double dist = totalAvail / (elements.size() - 1); double d = topEdge; for (int i = 1; i < elements.size() -1; i++) { d += dist; AlignElement e = elements.get(i); double my = d - e.rotatedBounds.getMinY(); d += e.rotatedBounds.getHeight(); DiagramGraphUtil.changeTransform(graph, e.element, new double[]{e.transform[0],e.transform[1],e.transform[2],e.transform[3],e.transform[4],my}); } break; } } } }); } /** * Rotate specified set of diagram element resources around the center of * mass of the specified element selection. * * @param resources diagram element resources to rotate * @param clockwise true to rotate 90 degrees clockwise, * false to rotate 90 degrees counter-clockwise */ public static void rotate(final Resource resources[], final boolean clockwise) { Simantics.getSession().asyncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { graph.markUndoPoint(); IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT); DiagramResource DIA = DiagramResource.getInstance(graph); List elements = new ArrayList(); List connections = new ArrayList(); for (Resource r : resources) { AlignElement e = create(graph, hints, r); if (e != null) elements.add(e); else if(graph.isInstanceOf(r, DIA.RouteGraphConnection)) connections.add(r); } if (elements.size() < 1) return; // Add comment to change set. CommentMetadata cm = graph.getMetadata(CommentMetadata.class); graph.addMetadata( cm.add("Rotate " + elements.size() + " elements " + (clockwise ? "clockwise" : "counter-clockwise")) ); AffineTransform at = clockwise ? AffineTransform.getQuadrantRotateInstance(1) : AffineTransform.getQuadrantRotateInstance(3); if (elements.size() == 1 && connections.isEmpty()) { for (AlignElement e : elements) { AffineTransform eat = new AffineTransform(e.transform[0], e.transform[1], e.transform[2], e.transform[3], 0, 0); eat.preConcatenate(at); DiagramGraphUtil.changeTransform(graph, e.element, new double[]{eat.getScaleX(),eat.getShearY(),eat.getShearX(),eat.getScaleY(),e.transform[4],e.transform[5]}); } } else { Rectangle2D selectionBounds = null; for (AlignElement e : elements) { if (selectionBounds != null) { selectionBounds.add(e.transform[4], e.transform[5]); } else { selectionBounds = new Rectangle2D.Double(e.transform[4], e.transform[5], 0, 0); } } double cx = selectionBounds.getCenterX(); double cy = selectionBounds.getCenterY(); for (AlignElement e : elements) { double x = e.transform[4]; double y = e.transform[5]; double dx = x - cx; double dy = y - cy; Point2D r = at.transform(new Point2D.Double(dx, dy), null); double mx = r.getX() + cx; double my = r.getY() + cy; AffineTransform eat = new AffineTransform(e.transform[0], e.transform[1], e.transform[2], e.transform[3], 0, 0); eat.preConcatenate(at); DiagramGraphUtil.changeTransform(graph, e.element, new double[]{eat.getScaleX(),eat.getShearY(),eat.getShearX(),eat.getScaleY(),mx,my}); } if(!connections.isEmpty()) { Command rotateConnection = Commands.get(graph, "Simantics/Diagram/rotateConnection"); Resource model = graph.syncRequest(new IndexRoot(connections.get(0))); for(Resource r : connections) rotateConnection.execute(graph, model, r, cx, cy, clockwise); } } } }); } /** * Flip specified set of diagram element resources around either the x or * y-axis specified by the mass center of the selection bounding box. * Each element is considered to weigh an equal amount. * * @param resources diagram element resources to flip * @param xAxis true to flip around x-axis, false * for y-axis */ public static void flip(final Resource resources[], final boolean xAxis) { Simantics.getSession().asyncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { graph.markUndoPoint(); IDiagram hints = Diagram.spawnNew(DiagramClass.DEFAULT); DiagramResource DIA = DiagramResource.getInstance(graph); List elements = new ArrayList(); List connections = new ArrayList(); for (Resource r : resources) { AlignElement e = create(graph, hints, r); if (e != null) elements.add(e); else if(graph.isInstanceOf(r, DIA.RouteGraphConnection)) connections.add(r); } if (elements.size() < 1) return; // Add comment to change set. CommentMetadata cm = graph.getMetadata(CommentMetadata.class); graph.addMetadata( cm.add("Flip " + elements.size() + " elements " + (xAxis ? "vertically" : "horizontally")) ); if (elements.size() == 1 && connections.isEmpty()) { for (AlignElement e : elements) { if (xAxis) { AffineTransform at = new AffineTransform(e.transform); AffineTransform at2 = AffineTransform.getScaleInstance(1, -1); at.preConcatenate(at2); DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],e.transform[5]}); } else { AffineTransform at = new AffineTransform(e.transform); AffineTransform at2 = AffineTransform.getScaleInstance(-1, 1); at.preConcatenate(at2); DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],e.transform[5]}); } } } else { Rectangle2D selectionBounds = null; for (AlignElement e : elements) { if (selectionBounds != null) { selectionBounds.add(e.transform[4], e.transform[5]); } else { selectionBounds = new Rectangle2D.Double(e.transform[4], e.transform[5], 0, 0); } } for (AlignElement e : elements) { if (xAxis) { double y = e.transform[5]; double cy = selectionBounds.getCenterY(); double my = cy + cy - y; AffineTransform at = new AffineTransform(e.transform); AffineTransform at2 = AffineTransform.getScaleInstance(1, -1); at.preConcatenate(at2); DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),e.transform[4],my}); } else { double x = e.transform[4]; double cx = selectionBounds.getCenterX(); double mx = cx + cx - x; AffineTransform at = new AffineTransform(e.transform); AffineTransform at2 = AffineTransform.getScaleInstance(-1, 1); at.preConcatenate(at2); DiagramGraphUtil.changeTransform(graph, e.element, new double[]{at.getScaleX(),at.getShearY(),at.getShearX(),at.getScaleY(),mx,e.transform[5]}); } } if(!connections.isEmpty()) { Command flipConnection = Commands.get(graph, "Simantics/Diagram/flipConnection"); Resource model = graph.syncRequest(new IndexRoot(connections.get(0))); for(Resource r : connections) flipConnection.execute(graph, model, r, xAxis, xAxis ? selectionBounds.getCenterY() : selectionBounds.getCenterX()); } } } }); } private static AlignElement create(ReadGraph graph, IDiagram hints, Resource r) throws ManyObjectsForFunctionalRelationException, NoSingleResultException, ServiceException, DatabaseException { DiagramResource dr = DiagramResource.getInstance(graph); if (graph.isInstanceOf(r, dr.Element) && !graph.isInstanceOf(r, dr.Connection) /*&& !graph.isInstanceOf(r, dr.Monitor)*/) { double transform[] = graph.getPossibleRelatedValue(r, dr.HasTransform); ElementClass ec = graph.syncRequest(DiagramRequests.getElementClass(graph.getSingleType(r, dr.Element), hints)); IElement e = Element.spawnNew(ec); Rectangle2D bounds = ElementUtils.getElementBounds(e); if (transform != null && bounds != null) { return new AlignElement(r, transform, bounds); } } return null; } private static class AlignElement { public Resource element; public double[] transform; public Rectangle2D bounds; public Rectangle2D rotatedBounds; // public Rectangle2D transformedBounds; public AlignElement(Resource element, double[] transform, Rectangle2D bounds) { this.element = element; this.transform = transform; this.bounds = bounds; // this.transformedBounds = getBounds(); this.rotatedBounds = getRotatedBounds(); } // public Rectangle2D getBounds() { // AffineTransform at = new AffineTransform(transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]); // return GeometryUtils.transformShape(bounds, at).getBounds2D(); // } public Rectangle2D getRotatedBounds() { AffineTransform at = new AffineTransform(transform[0], transform[1], transform[2], transform[3], 0.0, 0.0); return GeometryUtils.transformShape(bounds, at).getBounds2D(); } @SuppressWarnings("unused") public double getDeterminant() { return transform[0] * transform[3] - transform[1] * transform[2]; } } private static class XComparator implements Comparator { @Override public int compare(AlignElement o1, AlignElement o2) { if (o1.transform[4] < o2.transform[4]) return -1; if (o1.transform[4] > o2.transform[4]) return 1; return 0; } } private static class YComparator implements Comparator { @Override public int compare(AlignElement o1, AlignElement o2) { if (o1.transform[5] < o2.transform[5]) return -1; if (o1.transform[5] > o2.transform[5]) return 1; return 0; } } /** * Set 2D affine transforms for the listed diagram element resources. * * @param elements diagram element resources to set transforms for * @param transforms transforms for each element */ public static Write setTransformRequest(final Collection elements) { return new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { for (TransformedObject element : elements) DiagramGraphUtil.changeTransform(graph, element.element, element.transform); } }; } /** * Set 2D affine transforms for the listed diagram element resources. * * @param undoContext the database undo context to use for the returned * request * @param elements diagram element resources to set transforms for * @param transforms transforms for each element * @param preConcatenate true to pre-concatenate the * transforms, false to concatenate */ public static Write concatenateTransformRequest( UndoContext undoContext, final Collection elements, final boolean preConcatenate) { return new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { for (TransformedObject element : elements) { AffineTransform at = DiagramGraphUtil.getTransform(graph, element.element); if (preConcatenate) at.preConcatenate(element.transform); else at.concatenate(element.transform); DiagramGraphUtil.setTransform(graph, element.element, at); } } }; } public static class TransformedObject { public final Resource element; public final AffineTransform transform; public TransformedObject(Resource element) { this.element = element; this.transform = new AffineTransform(); } public TransformedObject(Resource element, AffineTransform transform) { this.element = element; this.transform = transform; } } // ------------------------------------------------------------------------ private interface TerminalFunction { /** * @param terminal * @param rotationTheta in radians * @return */ AffineTransform transform(Resource connectionPoint, double rotationTheta); default Point2D position(Resource connectionPoint, double rotationTheta) { AffineTransform tr = transform(connectionPoint, rotationTheta); return new Point2D.Double(tr.getTranslateX(), tr.getTranslateY()); } Resource toConnectionPoint(Resource terminal); Resource toTerminal(Resource connectionPoint); Set connectionPoints(); Set terminals(); } private static TerminalFunction terminalPositionFunction(ReadGraph graph, boolean ignoreElementRotation, Resource element) throws DatabaseException { AffineTransform transform = DiagramGraphUtil.getAffineTransform(graph, element); if (ignoreElementRotation) { AffineTransform noRotation = AffineTransform.getTranslateInstance(transform.getTranslateX(), transform.getTranslateY()); Point2D scale = org.simantics.scenegraph.utils.GeometryUtils.getScale2D(transform); noRotation.scale(scale.getX(), scale.getY()); transform = noRotation; } AffineTransform elementTransform = transform; TerminalMap elementTerminals = DiagramGraphUtil.getElementTerminals(graph, element); Map terminalTransforms = new HashMap<>(); for (Resource terminal : elementTerminals.getTerminals()) terminalTransforms.put(elementTerminals.getConnectionPoint(terminal), DiagramGraphUtil.getAffineTransform(graph, terminal)); return new TerminalFunction() { @Override public AffineTransform transform(Resource connectionPoint, double rotationTheta) { AffineTransform tr = new AffineTransform(elementTransform); AffineTransform terminalTr = terminalTransforms.get(connectionPoint); if (rotationTheta != 0) tr.rotate(rotationTheta); if (terminalTr != null) tr.concatenate(terminalTr); return tr; } @Override public Resource toConnectionPoint(Resource terminal) { return elementTerminals.getConnectionPoint(terminal); } @Override public Resource toTerminal(Resource connectionPoint) { return elementTerminals.getTerminal(connectionPoint); } @Override public Set connectionPoints() { return elementTerminals.getConnectionPoints(); } @Override public Set terminals() { return elementTerminals.getTerminals(); } }; } private static Set connectedTo(ReadGraph graph, Resource element, Resource connectionPoint) throws DatabaseException { BasicResources BR = BasicResources.getInstance(graph); Set result = new THashSet<>(); for (Resource connector : graph.getObjects(element, connectionPoint)) { Resource connection = graph.getPossibleObject(connector, BR.DIA.IsConnectorOf); if (connection == null) continue; for (Resource otherConnector : graph.getObjects(connection, BR.DIA.HasConnector)) { if (!otherConnector.equals(connector)) { for (Statement s : graph.getStatements(otherConnector, BR.STR.Connects)) { if (!s.getObject().equals(connection)) { result.add( new Terminal(s.getObject(), graph.getInverse(s.getPredicate())) ); } } } } } return result; } /** * Rotates provided element so that its rotation is the same as the slope of a * line drawn between the two other elements the element is connected to. * *

* Note that this requires that the element is connected to exactly two other * elements. * * @param graph * @param element * @return the chosen rotation in degrees * @throws DatabaseException */ public static double rotateToNeighborSlope(WriteGraph graph, Resource element) throws DatabaseException { if (LOGGER.isDebugEnabled()) LOGGER.debug("rotateAccordingToNeighborPositions( {} )", graph.getPossibleURI(element)); TerminalFunction elementTerminals = terminalPositionFunction(graph, true, element); Set connectedComponents = new HashSet<>(); Map componentTerminals = new HashMap<>(); Map connectedTerminals = new HashMap<>(); for (Resource connectionPoint : elementTerminals.connectionPoints()) { Set connectedTo = connectedTo(graph, element, connectionPoint); if (connectedTo.isEmpty()) continue; if (connectedTo.size() > 1) { if (LOGGER.isWarnEnabled()) LOGGER.warn("rotateToNeighbors only supports functional connection points, ignoring {}", NameUtils.getURIOrSafeNameInternal(graph, connectionPoint)); continue; } Terminal t = connectedTo.iterator().next(); TerminalFunction tf = terminalPositionFunction(graph, false, t.getComponent()); connectedComponents.add(t.getComponent()); componentTerminals.put(t.getComponent(), tf); connectedTerminals.put(connectionPoint, t); if (LOGGER.isDebugEnabled()) LOGGER.info("{} {} is connected to {}", NameUtils.getSafeName(graph, element), NameUtils.getSafeName(graph, connectionPoint), t.toString(graph)); } if (connectedComponents.size() != 2) { LOGGER.error("rotateToNeighborPositions only works for elements connected to exactly two other elements, {} is connected to {}", NameUtils.getURIOrSafeNameInternal(graph, element), connectedComponents.size()); return Double.NaN; } Resource[] ends = connectedComponents.toArray(Resource.NONE); AffineTransform[] endTr = { componentTerminals.get(ends[0]).transform(null, 0), componentTerminals.get(ends[1]).transform(null, 0) }; double slopeTheta = Math.atan2(endTr[1].getTranslateY() - endTr[0].getTranslateY(), endTr[1].getTranslateX() - endTr[0].getTranslateX()); double[] thetas = { slopeTheta, slopeTheta + Math.PI }; double selectedRotation = slopeTheta; double minCost = Double.MAX_VALUE; for (int i = 0; i < 2; ++i) { double cost = 0; for (Map.Entry e : connectedTerminals.entrySet()) { Terminal t = e.getValue(); TerminalFunction tf = componentTerminals.get(t.getComponent()); Point2D otp = tf.position(t.getRelation(), 0); Point2D etp = elementTerminals.position(e.getKey(), thetas[i]); cost += otp.distance(etp); } if (LOGGER.isDebugEnabled()) LOGGER.info("total cost of theta {} is {}", Math.toDegrees(thetas[i]), cost); if (cost < minCost) { minCost = cost; selectedRotation = thetas[i]; if (LOGGER.isDebugEnabled()) LOGGER.debug("new minimum cost {} found", cost); } } AffineTransform newTr = elementTerminals.transform(null, 0); newTr.rotate(selectedRotation); DiagramGraphUtil.setTransform(graph, element, newTr); if (LOGGER.isDebugEnabled()) LOGGER.debug("set rotation to {} degrees", Math.toDegrees(selectedRotation)); return Math.toDegrees(selectedRotation); } }