/******************************************************************************* * Copyright (c) 2009-2013 CWI * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Paul Klint - Paul.Klint@cwi.nl - CWI * * Arnold Lankamp - Arnold.Lankamp@cwi.nl *******************************************************************************/ package org.rascalmpl.eclipse.library.vis.figure.graph.layered; import static org.rascalmpl.eclipse.library.vis.properties.Properties.DIR; import static org.rascalmpl.eclipse.library.vis.properties.Properties.HGAP; import static org.rascalmpl.eclipse.library.vis.properties.Properties.HSIZE; import static org.rascalmpl.eclipse.library.vis.properties.Properties.ID; import static org.rascalmpl.eclipse.library.vis.properties.Properties.LAYER; import static org.rascalmpl.eclipse.library.vis.properties.Properties.VGAP; import static org.rascalmpl.eclipse.library.vis.properties.Properties.VSIZE; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import org.rascalmpl.interpreter.utils.RuntimeExceptionFactory; import org.rascalmpl.eclipse.library.vis.figure.Figure; import org.rascalmpl.eclipse.library.vis.figure.FigureFactory; import org.rascalmpl.eclipse.library.vis.graphics.GraphicsContext; import org.rascalmpl.eclipse.library.vis.properties.PropertyManager; import org.rascalmpl.eclipse.library.vis.swt.IFigureConstructionEnv; import org.rascalmpl.eclipse.library.vis.swt.applet.IHasSWTElement; import org.rascalmpl.eclipse.library.vis.util.vector.Rectangle; import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IList; import io.usethesource.vallang.IString; import io.usethesource.vallang.IValue; import io.usethesource.vallang.IValueFactory; import org.rascalmpl.values.ValueFactoryFactory; /** * Layered Graph layout. Given a list of nodes and edges a graph layout is computed with given size. * * We use a layered drawing method inspired by (but largely extended and tailored): * * Battista, et. al Graph Drawing, Prentice Hall, 1999 * * and various other publications listed in the code below. * * This graph layout can be selected with the property hint("layered") * * @author paulk * */ public class LayeredGraph extends Figure { protected ArrayList<LayeredGraphNode> nodes; protected ArrayList<LayeredGraphEdge> edges; protected HashMap<String, LayeredGraphNode> registeredNodeIds; LinkedList<LinkedList<LayeredGraphNode>> layers; protected HashMap<String, LinkedList<LayeredGraphNode>> registeredLayerIds; protected IFigureConstructionEnv fpa; double hgap; double vgap; double MAXWIDTH; private static final int INFINITY = Integer.MAX_VALUE - 1000; private static final double DINFINITY = Double.MAX_VALUE -1000; private static final boolean debug = false; private boolean doneLayout = false; public LayeredGraph(IFigureConstructionEnv fpa, PropertyManager properties, Figure[] nodes, IList edges) { super(properties); this.fpa = fpa; // Create the nodes this.nodes = new ArrayList<LayeredGraphNode>(); registeredNodeIds = new HashMap<String,LayeredGraphNode>(); registeredLayerIds = new HashMap<String, LinkedList<LayeredGraphNode>>(); ArrayList<Figure> listOfChildren = new ArrayList<Figure>(); for(Figure fig : nodes){ //children.add(fig); String name = fig.prop.getStr(ID); String layer = fig.prop.getStr(LAYER); if(name.length() == 0) throw RuntimeExceptionFactory.figureException("Id property should be defined", Figure.VF.bool(false), fpa.getRascalContext().getCurrentAST(), fpa.getRascalContext().getStackTrace()); if(getRegisteredNodeId(name) != null) throw RuntimeExceptionFactory.figureException("Id property is doubly declared", Figure.VF.bool(false), fpa.getRascalContext().getCurrentAST(), fpa.getRascalContext().getStackTrace()); LayeredGraphNode node = new LayeredGraphNode(this, name, fig); this.nodes.add(node); registerNodeId(name, node); listOfChildren.add(node); if(layer.length() > 0){ registerLayerId(layer, node); } } // Create the edges this.edges = new ArrayList<LayeredGraphEdge>(); for (IValue v : edges) { IConstructor c = (IConstructor) v; PropertyManager pm = c.arity() > 2 ? new PropertyManager(fpa, properties, (IList) c.get(2)) : properties; LayeredGraphEdge e = FigureFactory.makeLayeredGraphEdge(this, fpa, c, pm); boolean done = false; for(LayeredGraphEdge other : this.edges){ if(other.getFrom() == e.getFrom() && other.getTo() == e.getTo()){ // Identical edge, jut skip it; if(debug) System.err.println("Found identical edge"); done = true; break; } else if(other.getFrom() == e.getTo() && other.getTo() == e.getFrom()){ // Reverse edge, copy its arrows if(debug)System.err.println("Found reverse edge"); Figure toArrow = e.toArrow; //if(toArrow != null) // children.add(toArrow); if(toArrow != null && other.fromArrow == null) other.fromArrow = toArrow; Figure fromArrow = e.fromArrow; //if(fromArrow != null) // children.add(fromArrow); if(fromArrow != null && other.toArrow == null) other.toArrow = fromArrow; other.getFrom().addIn(e.getFrom()); other.getTo().addOut(e.getTo()); done = true; break; } } if(!done){ this.edges.add(e); e.getFrom().addOut(e.getTo()); e.getTo().addIn(e.getFrom()); } listOfChildren.add(e); // <====== if(e.label != null) listOfChildren.add(e.label); } this.children = new Figure[listOfChildren.size()]; this.children = listOfChildren.toArray(this.children); //this.children = new Figure[0]; if(debug){ for(LayeredGraphEdge e : this.edges){ System.err.println(" xx edge: " + e.getFrom().name + " -> " + e.getTo().name + " toArrow=" + e.toArrow + " fromArrow=" + e.fromArrow); } } } @Override public void computeMinSize() { if(doneLayout) return; doneLayout = true; minSize.setX(prop.getReal(HSIZE)); MAXWIDTH = Integer.MAX_VALUE; minSize.setY(prop.getReal(VSIZE)); hgap = prop.getReal(HGAP); vgap = prop.getReal(VGAP); //switchWidthAndHeight(); computeGraphLayout(); alignToSmallest(); translateToOrigin(); placeLabels(layers); translateToOrigin(); //switchWidthAndHeight(); rotateToDirection(); //setGraphMinSize(); int nChildren = nodes.size() + edges.size(); int nedges = edges.size(); this.children = new Figure[nChildren]; System.err.println("*** nChildren = " + nChildren); for(int i = 0; i < nedges; i++) this.children[i] = edges.get(i); for(int i = 0; i < nodes.size(); i++) this.children[nedges + i] = nodes.get(i); for(Figure fig : children) fig.computeMinSize(); System.err.println("*** length = " + this.children.length); } @Override public void resizeElement(Rectangle view) { localLocation.set(0,0); /* for(LayeredGraphNode g : nodes){ if(g.figure != null){ g.figure.localLocation.set(g.x - g.figure.minSize.getX()/2, g.y - g.figure.minSize.getY()/2); } } */ } @Override public void drawElement(GraphicsContext gc, List<IHasSWTElement> visibleSWTElements){ System.err.println("DrawElement for LayeredGraph"); /* applyProperties(gc); gc.translate(globalLocation.getX(), globalLocation.getY()); //for(LayeredGraphEdge e : edges){ // e.drawElement(gc, visibleSWTElements); //} for(LayeredGraphEdge e1 : edges){ if(e1.label != null){ e1.label.globalLocation.setX(e1.labelX); e1.label.globalLocation.setY(e1.labelY - e1.label.minSize.getY()/2); e1.label.drawElement(gc,visibleSWTElements); } } gc.translate(-globalLocation.getX(), -globalLocation.getY()); System.out.printf("Going back %s\n",globalLocation); */ } /* public boolean initChildren(IFigureConstructionEnv env, NameResolver resolver, MouseOver mparent, boolean swtSeen, boolean visible) { super.initChildren(env, resolver, mparent, swtSeen, visible); for(LayeredGraphEdge e : edges){ if(e.fromArrow!=null){ e.fromArrow.init(env, resolver, mparent, swtSeen, visible); } if(e.toArrow!=null){ e.toArrow.init(env, resolver, mparent, swtSeen, visible); } if(e.label!=null){ e.label.init(env, resolver, mparent, swtSeen, visible); } } return false; } */ /* public void resizeChildren(Rectangle view, TransformMatrix transform) { for(LayeredGraphEdge e : edges){ if(e.fromArrow!=null){ e.fromArrow.localLocation.set(0,0); e.fromArrow.globalLocation.set(0,0); e.fromArrow.size.set(minSize); e.fromArrow.resize(view,transform); } if(e.toArrow!=null){ e.toArrow.localLocation.set(0,0); e.toArrow.globalLocation.set(0,0); e.toArrow.size.set(minSize); e.toArrow.resize(view,transform); } if(e.label!=null){ e.label.localLocation.set(0,0); e.label.globalLocation.set(0,0); e.label.size.set(minSize); e.label.resize(view,transform); } } super.resizeChildren(view, transform); } */ /** * Associate name with node nd * @param name * @param nd */ public void registerNodeId(String name, LayeredGraphNode nd){ registeredNodeIds.put(name, nd); } /** * Get the node associated with name * @param name * @return */ public LayeredGraphNode getRegisteredNodeId(String name) { return registeredNodeIds.get(name); } public void registerLayerId(String name, LayeredGraphNode nd){ LinkedList<LayeredGraphNode> L = registeredLayerIds.get(name); if(L == null){ L = new LinkedList<LayeredGraphNode> (); } L.add(nd); registeredLayerIds.put(name, L); } public LinkedList<LayeredGraphNode> getRegisteredLayer(String name){ System.err.println("getRegisteredLayer: " + name); return registeredLayerIds.get(name); } /** * Print msg followed by the layers in readable format * @param msg * @param layers */ private void print(String msg, LinkedList<LinkedList<LayeredGraphNode>> layers){ System.err.println("---- " + msg); for(int i = 0; i < layers.size(); i++){ LinkedList<LayeredGraphNode> layer = layers.get(i); if(i > 0) System.err.println(""); System.err.print("Layer " + i + ": "); for(int j = 0; j < layer.size(); j++){ LayeredGraphNode g = layer.get(j); System.err.print("(" + j + ", " + g.name + ") "); } } System.err.println("\n----"); } private void printList(String msg, LinkedList<LayeredGraphNode> nodes){ System.err.printf("%s: [ ", msg); for(LayeredGraphNode g : nodes) System.err.printf("%s ", g.name); System.err.printf("]\n"); } private void printGraph(String txt){ System.err.println(txt); for(LayeredGraphNode g : nodes){ g.print(); } } /** * Translate all nodes to position top/left of graph at origin (0.0) * and compute actual dimensions */ private void translateToOrigin(){ double minx = Double.MAX_VALUE; double maxx = Double.MIN_VALUE; double miny = Double.MAX_VALUE; double maxy = Double.MIN_VALUE; for (LayeredGraphNode n : nodes) { double w2 = n.width() / 2; double h2 = n.height() / 2; if(n.x < 0) System.err.println(n.name + " has negative x: " + n.x); if (n.x - w2 < minx) minx = n.x - w2; if (n.x + w2 > maxx) maxx = n.x + w2; if (n.y - h2 < miny) miny = n.y - h2; if (n.y + h2 > maxy) maxy = n.y + h2; } for (LayeredGraphEdge e : edges) { if(e.label != null){ double w = e.label.minSize.getX(); double h2 = e.label.minSize.getY() / 2; System.err.println("labelX = " + e.labelX); if (e.labelX < minx) minx = e.labelX; if (e.labelX + w > maxx) maxx = e.labelX + w; if (e.labelY - h2 < miny) miny = e.labelY - h2; if (e.labelY + h2 > maxy) maxy = e.labelY + h2; } } System.err.printf("minx=%f, maxx=%f; miny=%f, maxy=%f\n", minx, maxx, miny, maxy); for (LayeredGraphNode n : nodes) { System.err.printf("%s: %f,%f -> %f,%f\n", n.name, n.x, n.y, n.x - minx, n.y - miny); n.x = n.x - minx; n.y = n.y - miny; } for(LayeredGraphEdge e : edges){ if(e.label != null){ e.labelX -= minx; e.labelY -= miny; } } minSize.setX(maxx - minx); minSize.setY(maxy - miny); } @SuppressWarnings("unused") private void switchWidthAndHeight(){ String dir = prop.getStr(DIR); if(dir.length() == 0 || dir.equals("TB") || dir.equals("BT")) return; for (LayeredGraphNode n : nodes){ n.exchangeWidthAndHeight(); } } /** * align the four layouts to the narrowest one: * - left top/bottom is alligned to minimum of narrowest * - right top/bottom is aligned to maximum of narrowest */ private void alignToSmallest(){ double minX[] = {DINFINITY, DINFINITY, DINFINITY, DINFINITY}; double maxX[] = {-1, -1, -1, -1}; for(Direction dir : Direction.dirs){ for(LayeredGraphNode n : nodes){ int k = Direction.ord(dir); if(n.x < minX[k]) minX[k] = n.x; if(n.x > maxX[k]) maxX[k] = n.x; } } double narrowest = DINFINITY; int dirNarrowest = -1; for(Direction dir : Direction.dirs){ int k = Direction.ord(dir); double w = maxX[k] - minX[k]; if(w < narrowest){ narrowest = w; dirNarrowest = k; } } double shifts[] = {0, 0, 0, 0}; for(Direction dir : Direction.dirs){ int k = Direction.ord(dir); if(Direction.isLeftDirection(dir)){ shifts[k] = minX[k] - minX[dirNarrowest]; } else shifts[k] = maxX[k] - maxX[dirNarrowest]; } for(LayeredGraphNode n : nodes){ n.shiftX(shifts); } } private void rotateToDirection(){ String dir = prop.getStr(DIR); if(dir.length() == 0 || dir.equals("TB")) return; if(dir.equals("BT")){ for (LayeredGraphNode n : nodes){ System.err.println("rotate: " + n.name + ": " + n.y + " -> " + (minSize.getY()- n.y)); n.y = minSize.getY() - n.y; } return; } double tmp; if(dir.equals("LR")){ for (LayeredGraphNode n : nodes){ tmp = n.x; n.x = n.y; n.y = minSize.getX() - tmp; } } else if(dir.equals("RL")){ for (LayeredGraphNode n : nodes){ tmp = n.x; n.x = minSize.getX() - n.y; n.y = tmp; } } tmp = minSize.getX(); minSize.setX(minSize.getY()); minSize.setY(tmp); } private int findSink(LinkedList<LayeredGraphNode> nlist){ int n = 0; for(LayeredGraphNode g : nlist){ if(g.isSink()) return n; n++; } return -1; } private int findSource(LinkedList<LayeredGraphNode> nlist){ int n = 0; for(LayeredGraphNode g : nlist){ if(g.isSource()) return n; n++; } return -1; } private int findLargestOutIn(LinkedList<LayeredGraphNode> nlist){ int n = -1; int max = -100000; for(int i = 0; i < nlist.size(); i++){ LayeredGraphNode g = nlist.get(i); int d = g.getOutInDiff(); if(d > max){ n = i; max = d; } } return n; } /** * Compute graph layout in the following stages: * - assignLayers (this includes cycle removal) * - insertVirtualNodes for edges that span more than one layer * - reduceCrossings by reordering nodes within each layer * - placeHorizontal by horizontally positioning nodes within each layer */ private void computeGraphLayout(){ if(layers == null){ if(debug) printGraph("Initial graph"); layers = assignLayers(MAXWIDTH); if(debug) print("assignLayers", layers); layers = insertVirtualNodes(layers); if(debug) print("insertVirtualNodes", layers); layers = reduceCrossings(layers); if(debug) print("reduceCrossings", layers); layers = moveInnerCrossingsDown(layers); if(debug) print("moveInnerCrossingsDown", layers); } placeHorizontal(layers); //placeLabels(layers); if(debug) printGraph("Final graph"); } /** * Order the nodes so that they are topologically ordered and remove cycles. * Uses Greedy-Cycle-Removal (Algorithm 9.4 in Battista et al.) * */ public void removeCycles(){ LinkedList<LayeredGraphNode> SL = new LinkedList<LayeredGraphNode>(); LinkedList<LayeredGraphNode> SR = new LinkedList<LayeredGraphNode>(); LinkedList<LayeredGraphNode> shadowNodes = new LinkedList<LayeredGraphNode>(); for(LayeredGraphNode g : nodes){ g.addShadowConnections(); shadowNodes.add(g); } while(shadowNodes.size() > 0){ do { int n = findSink(shadowNodes); if(n >= 0){ LayeredGraphNode g = shadowNodes.remove(n); g.disconnect(); SR.addFirst(g); if(debug)System.err.printf("add sink %s\n", g.name); } else break; } while(true); do { int n = findSource(shadowNodes); if(n >= 0){ LayeredGraphNode g = shadowNodes.remove(n); g.disconnect(); SL.addLast(g); if(debug)System.err.printf("add source %s\n", g.name); } else break; } while(true); if(debug)System.err.println("Sinks and sources added"); if(shadowNodes.size() > 0){ int max = findLargestOutIn(shadowNodes); if(max >= 0){ LayeredGraphNode g = shadowNodes.removeFirst(); SL.addLast(g); g.disconnect(); if(debug)System.err.printf("add node %s\n", g.name); } } } // Set the label in the original nodes. int n = 0; // First nodes in SL ... for(LayeredGraphNode g : SL) g.label = n++; // ... and then the nodes in SR for(LayeredGraphNode g : SR) g.label = n++; // Reverse all edges that go upwards and obtain an a-cyclical graph for(LayeredGraphEdge e : edges){ if(e.getFrom().label > e.getTo().label){ if(debug)System.err.println("Inverting " + e.getFrom().name + " => " + e.getTo().name); e.reverse(); } } for(LayeredGraphNode g : nodes){ g.delShadowConnections(); } } /** * Assign nodes to layers using Coffman-Graham-Layering (Alg. 9.1 in Battista et al) * @param W TODO unused * @return */ private LinkedList<LinkedList<LayeredGraphNode>> assignLayers(double W){ if(nodes.size() == 0) return new LinkedList<LinkedList<LayeredGraphNode>>(); removeCycles(); if(debug) for(LayeredGraphNode g : nodes){ System.err.printf("Node %s, label=%d, isSource=%b, isSink=%b\n", g.name, g.label, g.isSource(), g.isSink()); } // Label the nodes (again!) as needed using a lexicographic ordering // defined on LayeredGraphNodes (using compare). LinkedList<LayeredGraphNode> worklist = new LinkedList<LayeredGraphNode>(); LinkedList<LayeredGraphNode> labeled = new LinkedList<LayeredGraphNode>(); for(LayeredGraphNode g : nodes){ worklist.add(g); g.label = -1; } // Choose unlabeled node such that the labels of its inputs are minimized int label = 0; while(!worklist.isEmpty()){ LayeredGraphNode current = null; for(LayeredGraphNode g : worklist){ //System.err.println("For " + g.name + " AllInLablled = " + g.AllInLabelled()); if(g.AllInLabelled()){ if(debug)System.err.println("Consider " + g.name); if(current != null) if(debug) System.err.println("compare(" + current.name + ", " + g.name + ") == " + current.compareTo(g)); if(current == null || current.compareTo(g) == 1) current = g; } } if(current == null) current = worklist.getFirst(); current.label = label++; if(debug)System.err.println("*** Label " + current.name + " with " + current.label); worklist.remove(current); labeled.add(current); } // Place the labeled nodes in layers LinkedList<LinkedList<LayeredGraphNode>> layers = new LinkedList<LinkedList<LayeredGraphNode>>(); LinkedList<LayeredGraphNode> currentLayer = new LinkedList<LayeredGraphNode>(); // We create the layers from bottom to top, i.e. sinks are placed in layer 0. LinkedList<LayeredGraphNode> forcedTopLayer = getRegisteredLayer("TOP"); LinkedList<LayeredGraphNode> forcedBottomLayer = getRegisteredLayer("BOTTOM"); if(forcedBottomLayer != null){ for(LayeredGraphNode g : forcedBottomLayer){ System.err.println("BOTTOM node " + g.name); labeled.remove(g); currentLayer.addLast(g); g.layer = layers.size(); } layers.addFirst(currentLayer); currentLayer = new LinkedList<LayeredGraphNode>(); } if(forcedTopLayer != null){ for(LayeredGraphNode g : forcedTopLayer){ System.err.println("TOP node " + g.name); labeled.remove(g); } } while(!labeled.isEmpty()){ // Choose a node with the largest label with all its outputs already assigned to layers LayeredGraphNode current = null; for(LayeredGraphNode g : labeled){ if(g.AllOutAssignedToLayers()){ if(current == null || g.label > current.label) current = g; } } if(current == null){ if(debug)System.err.println("current is null"); current = labeled.getLast(); if(debug)System.err.println("pick current: " + current.name); } if(debug)System.err.println("current is " + current.name); labeled.remove(current); if (currentLayer.size() < W && (current.AllOutAssignedToLayers(layers.size()) || currentLayer.size() == 0)){ currentLayer.addLast(current); current.layer = layers.size(); } else { layers.addFirst(currentLayer); currentLayer = new LinkedList<LayeredGraphNode>(); currentLayer.addLast(current); current.layer = layers.size(); } if(debug)System.err.println("*** Assign " + current.name + " to layer " + current.layer); LinkedList<LayeredGraphNode> forcedSameLayer = getRegisteredLayer(current.getLayer()); if(forcedSameLayer != null && forcedSameLayer.size() > 0){ for(LayeredGraphNode g : forcedSameLayer){ if(g != current){ currentLayer.addLast(g); g.layer = layers.size(); labeled.remove(g); System.err.println("*** Assign " + g.name + " to same layer (" + g.layer + ")"); } } layers.addFirst(currentLayer); currentLayer = new LinkedList<LayeredGraphNode>(); } } if(forcedTopLayer != null && forcedTopLayer.size() > 0){ layers.addFirst(currentLayer); currentLayer = new LinkedList<LayeredGraphNode>(); for(LayeredGraphNode g : forcedTopLayer){ currentLayer.addLast(g); g.layer = layers.size(); } } if(currentLayer.size() > 0) layers.addFirst(currentLayer); // Since we assume in the other methods that the layers are numbered from top (0) to bottom // We reverse and correct the layer field in each node before returning. for(int h = 0; h < layers.size(); h++){ LinkedList<LayeredGraphNode> layer = layers.get(h); for(LayeredGraphNode g :layer){ g.layer = h; } } return moveSourcesDown(moveSinksUp(layers)); } private LinkedList<LinkedList<LayeredGraphNode>> moveSinksUp(LinkedList<LinkedList<LayeredGraphNode>> layers){ for(int l = 0; l < layers.size(); l++){ LinkedList<LayeredGraphNode> layer = layers.get(l); LinkedList<LayeredGraphNode> moved = new LinkedList<LayeredGraphNode>(); for(int i = 0; i < layer.size(); i++){ LayeredGraphNode g = layer.get(i); LayeredGraphNode p = g.lowestIn(); if(p != null && g.layer - p.layer > 1 && g.getLayer().length() == 0){ int newLayer = p.layer + 1; layers.get(newLayer).add(g); g.layer = newLayer; moved.add(g); } } for(LayeredGraphNode g : moved) layer.remove(g); } return layers; } private LinkedList<LinkedList<LayeredGraphNode>> moveSourcesDown(LinkedList<LinkedList<LayeredGraphNode>> layers){ for(int l = 0; l < layers.size(); l++){ LinkedList<LayeredGraphNode> layer = layers.get(l); LinkedList<LayeredGraphNode> moved = new LinkedList<LayeredGraphNode>(); for(int i = 0; i < layer.size(); i++){ LayeredGraphNode g = layer.get(i); LayeredGraphNode child = g.highestOut(); if(child != null && child.layer - g.layer > 1 && g.getLayer().length() == 0){ int newLayer = child.layer - 1; layers.get(newLayer).add(g); g.layer = newLayer; moved.add(g); } } for(LayeredGraphNode g : moved) layer.remove(g); } return layers; } /** * Insert a virtual node between nodes from and to. The nodes may be above or below each other. * @param layers of the graph * @param from start node * @param to end node */ private void insertVirtualNode(LinkedList<LinkedList<LayeredGraphNode>> layers, LayeredGraphNode orgFrom, LayeredGraphNode to){ LayeredGraphNode from = orgFrom; boolean downwards = from.isAbove(to); int delta = downwards ? 1 : -1; Figure orgEdgeLabel = null; while(Math.abs(to.layer - from.layer) > 1 && !to.hasVirtualOutTo(from)){ if(debug)System.err.println("insertVirtualNode: " + from.name + "-> " + to.name); // Create virtual node String fromName = from.name; int fni = from.name.indexOf("_"); if(fni > 0) fromName = fromName.substring(0, fni); String vname = fromName + "_" + to.name + "[" + (from.layer + delta) + "]"; if(debug)System.err.println("Creating virtual node " + vname + " between " + fromName + " and " + to.name); LayeredGraphNode virtual = new LayeredGraphNode(this, vname, hgap/2, vgap); IValueFactory vf = ValueFactoryFactory.getValueFactory(); IString vfVname = vf.string(vname); nodes.add(virtual); registerNodeId(vname, virtual); LinkedList<LayeredGraphNode> vlayer = layers.get(from.layer+delta); virtual.layer = from.layer + delta; virtual.pos = vlayer.size(); virtual.in.add(from); virtual.out.add(to); from.out.set(from.out.indexOf(to), virtual); to.in.set(to.in.indexOf(from), virtual); vlayer.add(virtual); LayeredGraphEdge old = null; for(LayeredGraphEdge e : edges){ if(debug)System.err.println("Consider edge " + e.getFrom().name + " -> " + e.getTo().name); if(e.getFrom() == from && e.getTo() == to){ old = e; if(from == orgFrom){ orgEdgeLabel = e.getLabel(); } if(debug)System.err.println("Removing old edge " + from.name + " -> " + to.name); break; } } //if(old == null) // throw RuntimeExceptionFactory.figureException("Internal error in insertVirtualNode", vfVname, fpa.getRascalContext().getCurrentAST(), fpa.getRascalContext().getStackTrace()); IString vfGname = vf.string(from.name); IString vfOname = vf.string(to.name); if(old != null){ if(old.isReversed()){ LayeredGraphEdge e1 = new LayeredGraphEdge(this, fpa, old.prop, vfGname, vfVname, old.fromArrow, old.toArrow); LayeredGraphEdge e2 = new LayeredGraphEdge(this, fpa, old.prop, vfVname, vfOname, old.fromArrow, old.toArrow); e2.label = orgEdgeLabel; edges.add(e1); edges.add(e2); } else { LayeredGraphEdge e1 = new LayeredGraphEdge(this, fpa, old.prop, vfGname, vfVname, old.toArrow, old.fromArrow); LayeredGraphEdge e2 = new LayeredGraphEdge(this, fpa, old.prop, vfVname, vfOname, old.toArrow, old.fromArrow); e2.label = orgEdgeLabel; edges.add(e1); edges.add(e2); } edges.remove(old); } from = virtual; } } /** * Insert virtual nodes in all layers * @param layers * @return layered graph extended with virtual nodes * Note: we assume here that insertVirtualNode adds virtual nodes at the END of nodes. */ private LinkedList<LinkedList<LayeredGraphNode>> insertVirtualNodes(LinkedList<LinkedList<LayeredGraphNode>> layers){ int n = nodes.size(); for(int i = 0; i < n; i++){ LayeredGraphNode g = nodes.get(i); for(LayeredGraphNode no : g.out){ insertVirtualNode(layers, g, no); } for(LayeredGraphNode ni : g.in){ insertVirtualNode(layers, ni, g); } } return layers; } private LinkedList<LinkedList<LayeredGraphNode>> estimateHorizontalPositions(LinkedList<LinkedList<LayeredGraphNode>> layers){ // Initial estimate of x positions (centered placement) for(int i = 0; i < layers.size(); i++){ LinkedList<LayeredGraphNode> layer = layers.get(i); // for(int j = 0; j < layer.size(); j++){ // LayeredGraphNode g = layer.get(j); // g.x = (j+1) * 50; // } double w = 0; for(int j = 0; j < layer.size(); j++){ LayeredGraphNode g = layer.get(j); w += g.width(); } w += (layer.size() - 1) * hgap; // account for gaps between nodes double x = (MAXWIDTH - w)/2; for(int j = 0; j < layer.size(); j++){ double wg = layer.get(j).width(); layer.get(j).x = x + wg/2; x += wg + hgap; } } return layers; } private LinkedList<LinkedList<LayeredGraphNode>> placeAtBaryCenters(LinkedList<LinkedList<LayeredGraphNode>> layers){ layers = estimateHorizontalPositions(layers); for(int i = 0; i < 5; i++) layers = placeAtBaryCenters1(layers); // Set all x positions back to uninitialized for(LayeredGraphNode g : nodes) g.x = -1; return layers; } /** * Place each node in each layer at its barycenter * @param layers * @return modified layers */ private LinkedList<LinkedList<LayeredGraphNode>> placeAtBaryCenters1(LinkedList<LinkedList<LayeredGraphNode>> layers){ LinkedList<LayeredGraphNode> empty = new LinkedList<LayeredGraphNode>(); // Now place each node at the barycenter of its neighbours for(int i = 0; i < layers.size(); i++){ if(debug)System.err.println("Layer " + i); LinkedList<LayeredGraphNode> P = (i == 0) ? empty : layers.get(i-1); LinkedList<LayeredGraphNode> L = layers.get(i); LinkedList<LayeredGraphNode> N = (i == layers.size()-1) ? empty : layers.get(i+1); LinkedList<LayeredGraphNode> LR = new LinkedList<LayeredGraphNode>(); for(int j = 0; j < L.size(); j++){ LayeredGraphNode g = L.get(j); if(debug)System.err.println("Node " + g.name); double baryCenter = g.baryCenter(P, N); double median = g.median(P); if(debug){System.err.println("median = " + median); System.err.println("baryCenter = " + baryCenter); System.err.println("x = " + g.x); } boolean added = false; for(int k = 0; k < LR.size(); k++){ LayeredGraphNode lrG = LR.get(k); if(baryCenter > lrG.baryCenter(P,N)){ g.x = baryCenter; LR.add(k,g); added = true; break; } if(k > 0 && baryCenter == lrG.x){ if(debug)System.err.println("Tie for " + g.name + " and " + lrG.name); double prevX = LR.get(k-1).x; g.x = prevX + (baryCenter - prevX)/2 -k; LR.add(k-1,g); added = true; break; } } if(!added) LR.addLast(g); } if(debug) printList("LR = ", LR); layers.set(i, LR); } return layers; } /** * Compute the number of crossings for two adjacent nodes u and v with nodes in the "previous" layer * Going down: L1 * L2 (u, v) * use the input of u and v * Going up: L2 (u,v) * L1 * use the outputs of u and v * @param down when true go down, otherwise go up. * @param L1 top layer (when down = true) * @param L2 bottom layer (when down = false) * @param u left node * @param v right node * @return the number of crossings */ private int cn(boolean down, LinkedList<LayeredGraphNode> L1, LinkedList<LayeredGraphNode> L2, LayeredGraphNode u, LayeredGraphNode v){ int n = 0; if(debug)System.err.printf("cn(%s,%s,%b)\n", u.name, v.name, down); assert L2.contains(u) && L2.contains(v); /* // LinkedList<LayeredGraphNode> L = down ? L2 : L1; LinkedList<LayeredGraphNode> L = down ? L1 : L2; for(LayeredGraphNode iu : down ? u.in : u.out){ for(LayeredGraphNode iv : down ? v.in : v.out){ if(L.indexOf(iu) > L.indexOf(iv)){ System.err.printf("Crossing for %s (%d) and %s (%d)\n", iu.name, L.indexOf(iu), iv.name, L.indexOf(iv)); n++; } } } */ if(down){ // going down: top layer L1, bottom layer L2, u,v in L2, inputs in L1 if(debug)printList("Neighbours " + u.name, u.getAllConnectedNeighbours(L1)); for(LayeredGraphNode iu : u.getAllConnectedNeighbours(L1)){ if(debug)printList("Neighbours " + v.name, v.getAllConnectedNeighbours(L1)); for(LayeredGraphNode iv : v.getAllConnectedNeighbours(L1)){ if(debug)System.err.printf("iu=%s, iv=%s\n", iu.name, iv.name); if(L1.indexOf(iu) > L1.indexOf(iv)){ if(debug)System.err.printf("Crossing for %s (%d) and %s (%d)\n", iu.name, L1.indexOf(iu), iv.name, L1.indexOf(iv)); n++; } } } } else { // going up: top Layer L2, bottom layer L1, u, v in L2, outputs in L1 if(debug)printList("Neighbours " + u.name, u.getAllConnectedNeighbours(L1)); for(LayeredGraphNode ou : u.getAllConnectedNeighbours(L1)){ if(debug)printList("Neighbours " + v.name, v.getAllConnectedNeighbours(L1)); for(LayeredGraphNode ov : v.getAllConnectedNeighbours(L1)){ if(debug)System.err.printf("ou=%s (index=%d), ov=%s (index=%d)\n", ou.name, L1.indexOf(ou), ov.name, L1.indexOf(ov)); if(L1.indexOf(ou) > L1.indexOf(ov)){ if(debug)System.err.printf("Crossing for %s (%d) and %s (%d)\n", ou.name, L1.indexOf(ou), ov.name, L1.indexOf(ov)); n++; } } } } if(debug)System.err.println("cn(" + u.name + ", " + v.name + (down ? ", down" : ", up") + ") -> " + n); return n; } private int exchangeAdjacentNodes(LinkedList<LayeredGraphNode> L1, LinkedList<LayeredGraphNode> L2, boolean down){ if(debug){ System.err.printf("exchangeAdjacentNodes: down = %b, L1= [ ", down); for(LayeredGraphNode g : L1) System.err.printf("%s ", g.name); System.err.printf("] L2 = [ "); for(LayeredGraphNode g : L2) System.err.printf("%s ", g.name); System.err.printf("]\n"); } int prevCrossings = INFINITY; int curCrossings = prevCrossings - 1; while(curCrossings < prevCrossings){ prevCrossings = curCrossings; curCrossings = 0; for(int j = 0; j < L2.size() - 1; j++){ LayeredGraphNode u = L2.get(j); LayeredGraphNode v = L2.get(j+1); if(debug)System.err.println("for node u=" + u.name + " (j = " + j + ") and node v=" + v.name); int cnbefore = cn(down, L1, L2, u, v); L2.set(j, v); v.pos = j; L2.set(j+1, u); u.pos = j+1; int cnafter = cn(down, L1, L2, v, u); if(debug)System.err.printf("j=%d, u=%s, v=%s, cnb=%d, cna=%d\n", j, u.name, v.name, cnbefore, cnafter); if(cnbefore > cnafter){ curCrossings += cnafter; if(debug)System.err.println("*** Exchange " + u.name + " and " + v.name); //} else if(cnbefore == cnafter){ // curCrossings += cnbefore; // System.err.println("*** Exchange (equal )" + u.name + " and " + v.name); } else { curCrossings += cnbefore; L2.set(j, u); u.pos = j; L2.set(j+1, v); v.pos = j+1; } } } if(debug)System.err.printf("exchangeAdjacentNodes => %d\n", prevCrossings); return prevCrossings; } /** * Reduce the number of crossings by making top/down and bottom/up sweeps across the layers and exchanging * nodes in a layer when appropriate. This is potentially extremely inefficient. * @param layers of the graph * @return the modified layers */ private LinkedList<LinkedList<LayeredGraphNode>> reduceCrossings(LinkedList<LinkedList<LayeredGraphNode>> layers){ layers = placeAtBaryCenters(layers); if(debug)print("reduceCrossings: placeAtBaryCenters done", layers); int prevCrossings = INFINITY; int curCrossings = prevCrossings - 1; int grace = 10; // grace more iterations when nothing seems to change for(int iter = 0; (curCrossings < prevCrossings) || grace-- > 0; iter += 2){ prevCrossings = curCrossings; curCrossings = 0; for(int k = 0; k < 2; k++){ boolean down = k == 0; if(debug){ System.err.println("=== iter = " + iter + ", " + (down ? "down" : "up") + " crossings = " + prevCrossings + " ==="); print("At start of iteration:", layers); } for(int i = down ? 0 : layers.size()-1; down ? (i <= layers.size()-2) : (i > 0); i += (down ? 1 : -1)){ if(debug)System.err.println("--- for layer i = " + i); curCrossings += exchangeAdjacentNodes(layers.get(i), layers.get(down ? i+1 : i-1), down); } } } if(debug)System.err.println("crossings = " + prevCrossings); // Assign layer position to each node; for(LinkedList<LayeredGraphNode> layer : layers){ for(int i = 0; i < layer.size(); i++) layer.get(i).pos = i; } return layers; } private LinkedList<LinkedList<LayeredGraphNode>> moveInnerCrossingsDown(LinkedList<LinkedList<LayeredGraphNode>> layers){ for(int i = 1; i < layers.size() - 1; i++){ LinkedList<LayeredGraphNode> lowerLayer = layers.get(i); for(int j = 0; j < lowerLayer.size() - 2; j++){ LayeredGraphNode w1 = lowerLayer.get(j); LayeredGraphNode w2 = lowerLayer.get(j+1); if(w1.isVirtual() && w2.isVirtual()){ LayeredGraphNode v1 = w1.in.get(0); LayeredGraphNode v2 = w2.in.get(0); System.err.println(" Exchange option: " + w1.name + " and " + w2.name); if(v1.pos > v2.pos){ System.err.println("Exchange " + w1.name + " and " + w2.name); int w1Pos = w1.pos; int w2Pos = w2.pos; lowerLayer.set(w1Pos, w2); w2.pos = w1Pos; lowerLayer.set(w2Pos, w1); w1.pos = w2Pos; } } } } return layers; } /* * Horizontal placement is based on * Ulrik Brandes & Boris Kopf, Fast and Simple Horizontal Coordinate Assignment, * Graph Drawing 2001, LNCS 2265, pp 31-44, 2002. */ private HashMap<LayeredGraphNode,LinkedList<LayeredGraphNode>> marked; private void mark(LayeredGraphNode from, LayeredGraphNode to){ LinkedList<LayeredGraphNode> associates = marked.get(from); if(associates == null) associates = new LinkedList<LayeredGraphNode>(); associates.add(to); marked.put(from, associates); } private boolean isMarked(LayeredGraphNode from, LayeredGraphNode to){ LinkedList<LayeredGraphNode> associates = marked.get(from); return associates == null ? false : associates.contains(to); } /** * Preprocess all nodes and mark "type 1" conflicts, i.e. crossings of an * inner segment (= segment between two virtual nodes) and a non-inner segment. * Resolve in favor of inner segment. * * @param layers */ private void preprocess(LinkedList<LinkedList<LayeredGraphNode>> layers){ marked = new HashMap <LayeredGraphNode,LinkedList<LayeredGraphNode>>(); int h = layers.size()-1; for(int i = 2; i <= h-2; i++){ int k0 = 0; int l = 0; LinkedList<LayeredGraphNode> Li = layers.get(i); int lastLi = Li.size() - 1; LinkedList<LayeredGraphNode> Lip1 = layers.get(i+1); int lastLip1 = Lip1.size()-1; for(int l1 = 0; l1 <= lastLip1; l1++){ LayeredGraphNode vl1 = Lip1.get(l1); boolean incidence = vl1.incidentToInnerSegment(Li); if(incidence) System.err.println("incidence for: " + vl1.name); if(l1 == lastLip1 || incidence){ int k1 = lastLi; if(incidence){ k1 = vl1.rightMostConnectedNeighbour(); } while (l <= l1){ LayeredGraphNode vl = Lip1.get(l); System.err.println("vl = " + vl.name); for(LayeredGraphNode vki : vl.getAllConnectedNeighbours(Li)){ System.err.println("vki = " + vki.name); int k = vki.pos; System.err.printf("k=%d, k0=%d, k1=%d\n", k, k0, k1); if(!(vl.isVirtual() && vki.isVirtual()) && (k < k0 || k > k1)){ if(debug)System.err.println("mark " + vki.name + " -> " + vl.name); vki.marked = true; mark(vki, vl); } } l++; } k0 = k1; } } } } private void alignVertical(LinkedList<LinkedList<LayeredGraphNode>> layers, Direction dir){ int layerStart, layerEnd, layerStep; boolean topAlign = Direction.isTopDirection(dir); boolean leftAlign = Direction.isLeftDirection(dir); System.err.printf("topAlign=%b, leftAlign=%b\n", leftAlign, topAlign); if(layers.size() <= 1) return; if(topAlign){ layerStart = 1; layerEnd = layers.size(); layerStep = 1; } else { layerStart = layers.size() - 2; layerEnd = -1; layerStep = -1; } for(int i = layerStart; i != layerEnd; i += layerStep){ LinkedList<LayeredGraphNode> Li = layers.get(i); LinkedList<LayeredGraphNode> neighbourLayer = layers.get(i + (topAlign ? -1 : 1)); int r = leftAlign ? -1 : neighbourLayer.size(); int nodeStart, nodeEnd, nodeStep; if(leftAlign){ nodeStart = 0; nodeEnd = Li.size(); nodeStep = 1; } else { nodeStart = Li.size() - 1; nodeEnd = -1; nodeStep = -1; } for(int k = nodeStart; k != nodeEnd; k += nodeStep){ LayeredGraphNode vk = Li.get(k); LinkedList<LayeredGraphNode> neighbours = vk.getAllConnectedNeighbours(neighbourLayer); //leftAlign ? vk.getAllConnectedNeighboursAfter(neighbourLayer, r) : //vk.getAllConnectedNeighboursBefore(neighbourLayer, r); if(debug)printList("vk = " + vk.name + ", r = " + r + ": ", neighbours); int d = neighbours.size(); if(d > 0){ int m1 = (d + 1)/2 - 1; int m2 = (d + 2)/2 - 1; int [] medians; if(m1 == m2) { medians = new int[] { m1 }; } else { medians = leftAlign ? new int[] { m1, m2 } : new int[] { m2, m1 }; } for(int m : medians){ if(vk.align == vk){ LayeredGraphNode um = neighbours.get(m); if(isMarked(um, vk)) System.err.println("Encountered marked edge: " + um.name + " -> " + vk.name); if(!isMarked(um, vk) && (leftAlign ? r < um.pos : um.pos < r)){ um.align = vk; /**/ um.blockWidth = Math.max(um.blockWidth, vk.width()); vk.root = um.root; vk.align = vk.root; r = um.pos; if(debug)System.err.printf("%s.align = %s, %s.root = %s, %s.align = %s\n", um.name, um.align.name, vk.name, vk.root.name, vk.name, vk.root.name); } } } } } } } private void placeBlock(LinkedList<LinkedList<LayeredGraphNode>> layers, LayeredGraphNode v, Direction dir){ if(debug)System.err.println("placeBlock: " + v.name + " x = " + v.getX(dir)); if(v.getX(dir) == -1){ boolean leftDir = Direction.isLeftDirection(dir); //v.setX(dir, leftDir ? v.root.blockWidth/2 : MAXWIDTH - v.root.blockWidth/2); v.setX(dir, leftDir ? v.width()/2 : MAXWIDTH - v.width()/2); LayeredGraphNode w = v; do { if(debug)System.err.println("placeBlock: v = " + v.name + "; v.x = " + v.getX(dir) + " w = " + w.name); LinkedList<LayeredGraphNode> layer = layers.get(w.layer); if(leftDir ? w.pos > 0 : w.pos < layer.size() - 1){ LayeredGraphNode u = layer.get(w.pos + (leftDir ? -1 : 1)).root; placeBlock(layers, u, dir); if(v.getSink() == v){ v.setSink(u.getSink()); if(debug)System.err.println("placeBlock: " + v.name + ".sink => " + u.getSink().name); } //double xDelta = hgap + (v.blockWidth + u.blockWidth)/2; double xDelta = hgap + (v.width() + u.blockWidth)/2; //double xDelta = hgap + (v.root.blockWidth + u.root.blockWidth)/2; if(v.getSink() != u.getSink()){ double s = leftDir ? v.getX(dir) - u.getX(dir) - xDelta : u.getX(dir) - v.getX(dir) - xDelta; u.getSink().setShift(leftDir ? Math.min(u.getSink().getShift(), s) : Math.max(u.getSink().getShift(), s)); if(debug)System.err.println("placeBlock: " + u.getSink().name + ".sink.shift => " + u.getSink().getShift() ); } else { v.setX(dir, leftDir ? Math.max(v.getX(dir), u.getX(dir) + xDelta) : Math.min(v.getX(dir), u.getX(dir) - xDelta)); if(debug)System.err.println(v.name + ".x -> " + v.getX(dir)); } } w = w.align; } while (w != v); } if(debug)System.err.println("placeBlock => " + v.name + " x = " + v.getX(dir)); } /** * Compact coherent blocks of nodes and assign x coordinates * @param layers of the graph * @param dir alignment direction */ private void compactHorizontal(LinkedList<LinkedList<LayeredGraphNode>> layers, Direction dir){ for(LayeredGraphNode v : nodes){ if(debug)System.err.println("compactHorizontal1: " + v.name); if(v.root == v) placeBlock(layers, v, dir); } boolean leftAligned = Direction.isLeftDirection(dir); for(LayeredGraphNode v : nodes){ if(debug)System.err.println("compactHorizontal2: " + v.name); v.setX(dir, v.root.getX(dir)); if(v.root == v && v.root.getSink().getShift() < DINFINITY){ if(leftAligned) v.setX(dir, v.getX(dir) + v.root.getSink().getShift()); else { v.setX(dir, v.getX(dir) - v.root.getSink().getShift()); } } if(debug)System.err.println("compactHorizontal3: " + v.name + ".x = " + v.getX(dir)); } } /** * Add y coordinates to all nodes * @param layers of the graph */ private void assignY(LinkedList<LinkedList<LayeredGraphNode>> layers){ double y = 0; for(LinkedList<LayeredGraphNode> layer : layers){ double hlayer = 0; for(LayeredGraphNode g : layer){ if(!g.isVirtual()){ hlayer = Math.max(hlayer, g.height()); } } for(LayeredGraphNode g : layer){ g.layerHeight = hlayer; //g.y = y + hlayer/2; g.y = y + g.height()/2; } y += hlayer + vgap; } } /** * * Perform horizontal placement for all 4 alignment directions and average the resulting x coordinates * @param layers of the graph * @return modified graph */ private LinkedList<LinkedList<LayeredGraphNode>>placeHorizontal(LinkedList<LinkedList<LayeredGraphNode>> layers){ assignY(layers); if(debug)System.err.println("assignY done"); preprocess(layers); if(debug)System.err.println("preprocess done"); boolean all = true; if(all){ for(Direction dir : Direction.dirs){ for(LayeredGraphNode g : nodes){ g.clearHorizontal(); } alignVertical(layers, dir); if(debug){ System.err.println("alignVertical done"); printGraph("after alignVertical"); } compactHorizontal(layers, dir); if(debug){ printGraph("after compactHorizontal"); System.err.println("compactHorizontal done"); } } for(LayeredGraphNode g : nodes){ g.averageHorizontal(); } } else { // Just for testing purposes // Direction dir = Direction.TOP_LEFT; Direction dir = Direction.TOP_RIGHT; // Direction dir = Direction.BOTTOM_LEFT; // Direction dir = Direction.BOTTOM_RIGHT; for(LayeredGraphNode g : nodes){ g.clearHorizontal(); } alignVertical(layers, dir); System.err.println("alignVertical done"); printGraph("after alignVertical"); compactHorizontal(layers, dir); printGraph("after compactHorizontal"); } return layers; } private LinkedList<LayeredGraphEdge> insertEdge(LinkedList<LayeredGraphEdge> layer, LayeredGraphEdge e){ for(int i = 0; i < layer.size(); i++){ LayeredGraphEdge f = layer.get(i); if(f.labelX > e.labelX){ layer.add(i, e); return layer; } } layer.add(e); return layer; } private void placeLabels(LinkedList<LinkedList<LayeredGraphNode>> layers){ LinkedList<LinkedList<LayeredGraphEdge>> labels = new LinkedList<LinkedList<LayeredGraphEdge>>(); for(int i = 0; i < layers.size(); i++){ labels.add(new LinkedList<LayeredGraphEdge>()); } for(LayeredGraphEdge e1 : edges){ if(e1.label != null){ System.err.println("placeLabels: " + e1.getFrom().name + "->" + e1.getTo().name); e1.setLabelCoordinates(); } } for(LayeredGraphEdge e : edges){ if(e.label != null){ int fl = e.getFrom().layer; int tl = e.getTo().layer; int level = fl < tl ? fl : tl; labels.set(level, insertEdge(labels.get(level), e)); } } for(LinkedList<LayeredGraphEdge> layerLabels : labels) LayeredGraphEdge.optimizeLabels(layerLabels); } }