Java tutorial
/* * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient * * http://www.informatik.uni-kiel.de/rtsys/kieler/ * * Copyright 2010 by * + Christian-Albrechts-University of Kiel * + Department of Computer Science * + Real-Time and Embedded Systems Group * * This code is provided under the terms of the Eclipse Public License (EPL). * See the file epl-v10.html for the license text. */ package de.cau.cs.kieler.klay.layered.p5edges; import java.util.EnumSet; import java.util.LinkedList; import java.util.ListIterator; import java.util.Set; import com.google.common.collect.Iterables; import de.cau.cs.kieler.core.alg.IKielerProgressMonitor; import de.cau.cs.kieler.core.math.BezierSpline; import de.cau.cs.kieler.core.math.CubicSplineInterpolator; import de.cau.cs.kieler.core.math.ISplineInterpolator; import de.cau.cs.kieler.core.math.KVector; import de.cau.cs.kieler.core.math.KVectorChain; import de.cau.cs.kieler.core.math.KielerMath; import de.cau.cs.kieler.core.math.BezierSpline.BezierCurve; import de.cau.cs.kieler.klay.layered.ILayoutPhase; import de.cau.cs.kieler.klay.layered.IntermediateProcessingConfiguration; import de.cau.cs.kieler.klay.layered.graph.LEdge; import de.cau.cs.kieler.klay.layered.graph.LNode; import de.cau.cs.kieler.klay.layered.graph.LPort; import de.cau.cs.kieler.klay.layered.graph.Layer; import de.cau.cs.kieler.klay.layered.graph.LGraph; import de.cau.cs.kieler.klay.layered.intermediate.LayoutProcessorStrategy; import de.cau.cs.kieler.klay.layered.properties.GraphProperties; import de.cau.cs.kieler.klay.layered.properties.NodeType; import de.cau.cs.kieler.klay.layered.properties.PortType; import de.cau.cs.kieler.klay.layered.properties.Properties; /** * Implements a naive way of routing the edges with splines. Uses the dummy nodes as reference * points for a spline calculation. This results in the bend points actually lying on the curve, * being the dummy nodes. * * <dl> * <dt>Precondition:</dt><dd>the graph has a proper layering with * assigned node and port positions; the size of each layer is * correctly set</dd> * <dt>Postcondition:</dt><dd>each node is assigned a horizontal coordinate; * the bend points of each edge are set; the width of the whole graph is set</dd> * </dl> * * @author uru * @author msp * @kieler.design 2012-08-10 chsch grh * @kieler.rating proposed yellow by msp */ public final class SplineEdgeRouter implements ILayoutPhase { /* The basic processing strategy for this phase is empty. Depending on the graph features, * dependencies on intermediate processors are added dynamically as follows: * * Before phase 1: * - None. * * Before phase 2: * - For center edge labels: * - LABEL_DUMMY_INSERTER * * Before phase 3: * - For edge labels: * - LABEL_SIDE_SELECTOR * * - For center edge labels: * - LABEL_SIDE_SELECTOR * - LABEL_DUMMY_SWITCHER * * Before phase 4: * - None. * * Before phase 5: * - None. * * After phase 5: * - For center edge labels: * - LABEL_DUMMY_REMOVER * * - For end edge labels: * - END_LABEL_PROCESSOR */ /** additional processor dependencies for graphs with center edge labels. */ private static final IntermediateProcessingConfiguration CENTER_EDGE_LABEL_PROCESSING_ADDITIONS = new IntermediateProcessingConfiguration( // Before Phase 1 null, // Before Phase 2 EnumSet.of(LayoutProcessorStrategy.LABEL_DUMMY_INSERTER), // Before Phase 3 EnumSet.of(LayoutProcessorStrategy.LABEL_SIDE_SELECTOR, LayoutProcessorStrategy.LABEL_DUMMY_SWITCHER), // Before Phase 4 null, // Before Phase 5 null, // After Phase 5 EnumSet.of(LayoutProcessorStrategy.LABEL_DUMMY_REMOVER)); /** additional processor dependencies for graphs with head or tail edge labels. */ private static final IntermediateProcessingConfiguration END_EDGE_LABEL_PROCESSING_ADDITIONS = new IntermediateProcessingConfiguration( // Before Phase 1 null, // Before Phase 2 null, // Before Phase 3 EnumSet.of(LayoutProcessorStrategy.LABEL_SIDE_SELECTOR), // Before Phase 4 null, // Before Phase 5 null, // After Phase 5 EnumSet.of(LayoutProcessorStrategy.END_LABEL_PROCESSOR)); /** factor for layer spacing. */ private static final double LAYER_SPACE_FAC = 0.2; /** the maximal angle that short edges may have (for larger angles a spline is created). */ private static final double MAXIMAL_ANGLE = Math.PI / 6; // SUPPRESS CHECKSTYLE MagicNumber /** amounts of points treated the same way. */ private static final int HIGH_LIMIT = 7; private static final int MID_LIMIT = 5; /** corresponding offsets of used dummy node positions. */ private static final int SMALL_OFFSET = 2; private static final int BIG_OFFSET = 3; /** how strong is the first and last ctrl point scaled down? new size is 1/VERTICAL_CHANGE. */ private static final double VERTICAL_CHANGE = 4; /** at least this many points are needed to handle the spline. */ private static final int MINIMAL_POINTS_HANDLES = 4; /** factor with which a control point is moved closer to the start/end point. */ private static final double SMOOTHNESS_FACTOR = 0.3d; /** * how long may the distance between the first/second control point and the start/end point be, * considering the distance between start and end. */ private static final double MAX_DISTANCE = 0.75d; /** the module for spline interpolation. */ private ISplineInterpolator interpolator = new CubicSplineInterpolator(); /** * {@inheritDoc} */ public IntermediateProcessingConfiguration getIntermediateProcessingConfiguration(final LGraph graph) { Set<GraphProperties> graphProperties = graph.getProperty(Properties.GRAPH_PROPERTIES); // Basic configuration IntermediateProcessingConfiguration configuration = new IntermediateProcessingConfiguration(); // Additional dependencies if (graphProperties.contains(GraphProperties.CENTER_LABELS)) { configuration.addAll(CENTER_EDGE_LABEL_PROCESSING_ADDITIONS); } if (graphProperties.contains(GraphProperties.END_LABELS)) { configuration.addAll(END_EDGE_LABEL_PROCESSING_ADDITIONS); } return configuration; } /** * {@inheritDoc} */ public void process(final LGraph layeredGraph, final IKielerProgressMonitor monitor) { monitor.begin("Spline edge routing", 1); double spacing = layeredGraph.getProperty(Properties.OBJ_SPACING); float edgeSpaceFac = layeredGraph.getProperty(Properties.EDGE_SPACING_FACTOR); double xpos = 0.0, layerSpacing = 0.0; for (Layer layer : layeredGraph) { boolean externalLayer = Iterables.all(layer, PolylineEdgeRouter.PRED_EXTERNAL_PORT); // the rightmost layer is not given any node spacing if (externalLayer && xpos > 0) { xpos -= spacing; } // set horizontal coordinates for all nodes of the layer layer.placeNodes(xpos); double maxVertDiff = 0; for (LNode node : layer) { // count the maximal vertical difference of output edges for (LPort port : node.getPorts(PortType.OUTPUT)) { double sourcePos = port.getNode().getPosition().y + port.getPosition().y + port.getAnchor().y; for (LPort targetPort : port.getSuccessorPorts()) { if (targetPort.getNode().getLayer() != node.getLayer()) { double targetPos = targetPort.getNode().getPosition().y + targetPort.getPosition().y + targetPort.getAnchor().y; maxVertDiff = KielerMath.maxd(maxVertDiff, targetPos - sourcePos, sourcePos - targetPos); } } } } // Determine placement of next layer based on the maximal vertical difference (as the // maximum vertical difference edges span grows, the layer grows wider to allow enough // space for such sloped edges to avoid too harsh angles) layerSpacing = LAYER_SPACE_FAC * edgeSpaceFac * maxVertDiff + 1; if (!externalLayer) { layerSpacing += spacing; } xpos += layer.getSize().x + layerSpacing; } layeredGraph.getSize().x = xpos - layerSpacing; // process all edges for (Layer layer : layeredGraph) { for (LNode node : layer) { NodeType sourceNodeType = node.getProperty(Properties.NODE_TYPE); if (sourceNodeType != NodeType.LONG_EDGE && sourceNodeType != NodeType.LABEL) { for (LPort port : node.getPorts()) { for (LEdge edge : port.getOutgoingEdges()) { NodeType targetNodeType = edge.getTarget().getNode().getProperty(Properties.NODE_TYPE); if (targetNodeType == NodeType.LONG_EDGE || targetNodeType == NodeType.LABEL) { processLongEdge(edge); } else { processShortEdge(edge); } } } } } } monitor.done(); } /** * Process a short edge. * * @param edge an edge */ private void processShortEdge(final LEdge edge) { LPort start = edge.getSource(); LPort end = edge.getTarget(); KVector startVec = start.getAbsoluteAnchor(); KVector endVec = end.getAbsoluteAnchor(); // it is enough to check one vector, as the angle at the other node is the same KVector startToEnd = KVector.diff(endVec, startVec); double radians = startToEnd.toRadians(); // if the minimalAngle criteria is not met, create a short spline if (radians > MAXIMAL_ANGLE || radians < -MAXIMAL_ANGLE) { BezierSpline spline = generateShortSpline(startVec, endVec); for (KVector v : spline.getInnerPoints()) { edge.getBendPoints().add(v); } } } /** * Process a long edge. * * @param edge the first short edge belonging to a long edge */ private void processLongEdge(final LEdge edge) { LEdge intermediateEdge = edge; KVectorChain points = new KVectorChain(); points.add(edge.getSource().getAbsoluteAnchor()); KVector startTangent = edge.getTarget().getAbsoluteAnchor().sub(points.getFirst()).normalize(); // run along the edge till end point is found do { LPort targetPort = intermediateEdge.getTarget(); for (LPort iterPort : targetPort.getNode().getPorts()) { if (iterPort.getOutgoingEdges().size() > 0) { intermediateEdge = iterPort.getOutgoingEdges().get(0); break; } } points.add(targetPort.getAbsoluteAnchor()); } while (intermediateEdge.getTarget().getNode().getProperty(Properties.NODE_TYPE) == NodeType.LONG_EDGE); points.add(intermediateEdge.getTarget().getAbsoluteAnchor()); KVector endTangent = intermediateEdge.getSource().getAbsoluteAnchor().sub(points.getLast()).normalize(); BezierSpline spline = generateSpline(points, startTangent, endTangent.negate()); // apply optimizations for NaiveApproach spline = optimizeSpline(spline); // add calculated bend points for (KVector v : spline.getInnerPoints()) { edge.getBendPoints().add(v); } } /** * Initial try to improve naive splines by post processing. * We try to reduce the funny curves by removing some control points and therefore "smoothing" * the curve. * * @param spline a spline * @return a new optimized spline */ private BezierSpline optimizeSpline(final BezierSpline spline) { if (spline.getBasePoints().length >= MINIMAL_POINTS_HANDLES && isLongStraightSpline(spline)) { KVector[] basePoints = spline.getBasePoints(); int n = basePoints.length - 1; int offset = (n >= HIGH_LIMIT) ? BIG_OFFSET : SMALL_OFFSET; if (n < MID_LIMIT) { offset = 1; } KVector start = spline.getStartPoint(); KVector end = spline.getEndPoint(); LinkedList<KVector> newPoints = new LinkedList<KVector>(); newPoints.add(start); if (n >= MID_LIMIT) { KVector intermediateLeft = basePoints[1].clone(); intermediateLeft.y += (start.y - intermediateLeft.y) / VERTICAL_CHANGE; newPoints.add(intermediateLeft); } KVector bendLeft = null; if (n >= MID_LIMIT) { bendLeft = basePoints[offset].clone(); newPoints.add(bendLeft); } KVector bendRight = basePoints[n - offset].clone(); newPoints.add(bendRight); if (n >= MID_LIMIT) { KVector intermediateRight = basePoints[n - 1].clone(); intermediateRight.y += (end.y - intermediateRight.y) / VERTICAL_CHANGE; newPoints.add(intermediateRight); } newPoints.add(end); KVector test = null; if (bendLeft == null) { test = bendRight.clone(); } KVector startTangent = KVector.diff((bendLeft == null) ? test : bendLeft, start).normalize(); KVector endTangent = KVector.diff(bendRight, end).normalize().negate(); return generateSpline(newPoints, startTangent, endTangent); } else { return spline; } } /** * Test whether this is a spline with a straight path between the dummy nodes. * * @param spline a spline * @return true if it is a long straight spline */ private boolean isLongStraightSpline(final BezierSpline spline) { KVector start = spline.getStartPoint(); KVector end = spline.getEndPoint(); Integer y = null; for (KVector pt : spline.getBasePoints()) { if (pt != start && pt != end) { if (y == null) { y = (int) pt.y; } else { if (!((int) pt.y == (int) y)) { return false; } } } } return true; } /** * Generates a simple piecewise bezier curve for given points. * * @param pArray * array with points which should be lay on the spline * @param vectorQ * outgoing tangent vector of the initial node * @param vectorS * incoming tangent vector of the final node * @return created spline */ public BezierSpline generateSpline(final LinkedList<KVector> pArray, final KVector vectorQ, final KVector vectorS) { BezierSpline spline; if (vectorQ == null || vectorS == null) { spline = interpolator.interpolatePoints(pArray); } else { spline = interpolator.interpolatePoints(pArray, vectorQ, vectorS, true); } // as the generated spline can contain loops etc, we try to remove those, by adjusting the // control points if (pArray.size() > 2) { removeFunnyCycles(spline); } return spline; } /** * Twiddles some control points, as there may be situations where they are inappropriate. * Function ensures that C1 derive ability is preserved. * * @param spline */ private void removeFunnyCycles(final BezierSpline spline) { ListIterator<BezierCurve> listIt = spline.getCurves().listIterator(); while (listIt.hasNext()) { BezierCurve curve = listIt.next(); double dist = KVector.distance(curve.start, curve.end); double distFst = KVector.distance(curve.start, curve.fstControlPnt); double distSnd = KVector.distance(curve.end, curve.sndControlPnt); // scale first ctrl point and therefore second of next curve if (distFst > dist * MAX_DISTANCE) { KVector v = KVector.diff(curve.fstControlPnt, curve.start); v.scaleToLength(dist * SMOOTHNESS_FACTOR); curve.fstControlPnt = KVector.sum(curve.start, v); if (listIt.hasPrevious()) { listIt.previous(); if (listIt.hasPrevious()) { BezierCurve tempCurve = listIt.previous(); KVector v1 = KVector.diff(tempCurve.sndControlPnt, tempCurve.end); v1.scaleToLength(dist * SMOOTHNESS_FACTOR); tempCurve.sndControlPnt = KVector.sum(tempCurve.end, v1); listIt.next(); } listIt.next(); } } // scale second ctrl point and therefore first of next curve if (distSnd > dist * MAX_DISTANCE) { KVector v = KVector.diff(curve.sndControlPnt, curve.end); v.scaleToLength(dist * SMOOTHNESS_FACTOR); curve.sndControlPnt = KVector.sum(curve.end, v); if (listIt.hasNext()) { BezierCurve tempCurve = listIt.next(); KVector v1 = KVector.diff(tempCurve.fstControlPnt, tempCurve.start); v1.scaleToLength(dist * SMOOTHNESS_FACTOR); tempCurve.fstControlPnt = KVector.sum(tempCurve.start, v1); listIt.previous(); } } } } /** * Generates a spline representation for straight edges. * * @param q * start point * @param s * end point * @return BezierSpline representation. */ public BezierSpline generateShortSpline(final KVector q, final KVector s) { // we choose width difference as distance for ctr points, as it showed good results. double widthdiff = Math.abs(q.x - s.x); KVector startTan = new KVector(widthdiff, 0); KVector endTan = new KVector(widthdiff, 0); BezierSpline spline = interpolator.interpolatePoints(new KVector[] { q, s }, startTan, endTan, false); return spline; } }