/******************************************************************************* * 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: * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI * * 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.LAYER; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import org.rascalmpl.eclipse.library.vis.figure.Figure; import org.rascalmpl.eclipse.library.vis.graphics.GraphicsContext; import org.rascalmpl.eclipse.library.vis.swt.applet.IHasSWTElement; import org.rascalmpl.eclipse.library.vis.util.vector.Coordinate; import org.rascalmpl.eclipse.library.vis.util.vector.Rectangle; /** * A GraphNode is created for each "node" constructor that occurs in the graph. * * @author paulk * */ public class LayeredGraphNode extends Figure /*implements Comparable<LayeredGraphNode>*/ { protected String name; protected Figure figure; protected double x = -1; protected double y = -1; private double[] xs = {-1f, -1f, -1f, -1f}; LayeredGraph graph; protected LinkedList<LayeredGraphNode> in; protected LinkedList<LayeredGraphNode> out; protected LinkedList<LayeredGraphNode> inShadowed; protected LinkedList<LayeredGraphNode> outShadowed; private boolean shadowing = false; // private static boolean debug = false; int label = -1; int layer = -1; int pos = -1; boolean marked = false; LayeredGraphNode root; LayeredGraphNode align; private LayeredGraphNode sink; final int INFINITY = Integer.MAX_VALUE; final double DINFINITY = Double.MAX_VALUE; private double shift = DINFINITY; public double layerHeight; public double blockWidth; private double virtWidth = 20; // Dimensions of a virtual node private double virtHeight = 20; LayeredGraphNode(LayeredGraph g, String name, Figure fig){ super(fig != null ? fig.prop : g.prop); this.graph = g; this.name = name; this.figure = fig; if(fig != null){ this.children = new Figure[1]; this.children[0] = fig; } else { this.children = childless; } in = new LinkedList<LayeredGraphNode>(); out = new LinkedList<LayeredGraphNode>(); root = align = sink = this; shift = DINFINITY; } LayeredGraphNode(LayeredGraph g, String name, double vw, double vh){ this(g, name, null); virtWidth = vw; virtHeight = vh; } public void init(){ x = y = -1; marked = false; root = align = sink = this; } public void computeMinSize(){ //resizable.set(false,false); } public void print(){ System.err.println("*** " + name); System.err.printf("\tin = "); for(LayeredGraphNode gin : in){ System.err.printf("%s ", gin.name); } System.err.println(""); System.err.printf("\tout = "); for(LayeredGraphNode gout : out){ System.err.printf("%s ", gout.name); } System.err.println(""); System.err.println("\tlabel = " + label); System.err.println("\tlayer = " + layer); System.err.println("\tpos = " + pos); System.err.println("\tlayerHeight = " + layerHeight); System.err.println("\troot = " + (root != null ? root.name: "null")); System.err.println("\talign = " + (align != null ? align.name: "null")); System.err.println("\tsink = " + (sink != null ? sink.name: "null")); System.err.println("\tsink.shift = " + (sink != null ? sink.shift : -1)); System.err.println("\tthis.X= " + x); System.err.println("\tthis.Y= " + y); System.err.println("\tblockWidth = " + blockWidth); System.err.println("\tthis.shift = " + shift); } /* Elementary operations on nodes */ public boolean isVirtual(){ return figure == null; } public void addIn(LayeredGraphNode n){ if(!in.contains(n)) in.add(n); } public void addOut(LayeredGraphNode n){ if(!out.contains(n)) out.add(n); } public boolean isAbove(LayeredGraphNode g){ return layer < g.layer; } public boolean isBelow(LayeredGraphNode g){ return layer > g.layer; } public void delIn(LayeredGraphNode n){ if(in.contains(n)) in.remove(n); } public void delOut(LayeredGraphNode n){ if(out.contains(n)) out.remove(n); } public boolean hasVirtualOutTo(LayeredGraphNode other){ String vname = "_" + other.name + "["; for(LayeredGraphNode g : out){ if(g.isVirtual() && g.name.contains(vname)) return true; } return false; } public int degree(){ return in.size() + out.size(); } public void exchangeWidthAndHeight(){ if(figure != null){ double tmp = figure.minSize.getX(); figure.minSize.setX(figure.minSize.getY()); figure.minSize.setY(tmp); } } public double baryCenter(LinkedList<LayeredGraphNode> above,LinkedList<LayeredGraphNode> below){ int sum = 0; LinkedList<LayeredGraphNode> aboveG = getAllConnectedNeighbours(above); LinkedList<LayeredGraphNode> belowG = getAllConnectedNeighbours(below); for(LayeredGraphNode ag : aboveG){ sum += ag.x; } for(LayeredGraphNode bg : belowG){ sum += bg.x; } int degree = aboveG.size() + belowG.size(); return degree > 0 ? sum/degree : 0; } public double median(LinkedList<LayeredGraphNode> above){ LinkedList<LayeredGraphNode> aboveG = getAllConnectedNeighbours(above); int nAbove = aboveG.size(); return nAbove > 0 ? aboveG.get(nAbove/2).x : 0; } /* Methods for ordering and cycle removal */ /** * Create a shadow graph for the purpose of cycle removal. * Add a copy of in and out to inShadowed, resp., outShadowed. * These are used during removeCycles and are null before and after * removeCycles has been called. */ public void addShadowConnections(){ shadowing = true; inShadowed = new LinkedList<LayeredGraphNode>(); outShadowed = new LinkedList<LayeredGraphNode>(); for(LayeredGraphNode i : in) inShadowed.add(i); for(LayeredGraphNode o : out) outShadowed.add(o); } /** * Remove the copies of in and out. */ public void delShadowConnections(){ inShadowed = outShadowed = null; shadowing = false; } /** * Disconnect this node from the graph during cycle removal */ public void disconnect(){ for(LayeredGraphNode g : inShadowed){ g.outShadowed.remove(this); } for(LayeredGraphNode g : outShadowed){ g.inShadowed.remove(this); } } public int maxLabel(){ return maxLabel(outShadowed); } private int maxLabel(LinkedList<LayeredGraphNode> a){ int m = -1; for(LayeredGraphNode g : a){ if(g.label > m) m = g.label; } return m; } /** * @return is this node a sink, i.e. has no outgoing edges? * Note: be called during and after shadowing */ public boolean isSink(){ return (shadowing ? outShadowed : out).size() == 0; } /** * @return is this node a source, i.e. has no incoming edges? * Note: may be called during and after shadowing. */ public boolean isSource(){ return (shadowing ? inShadowed : in).size() == 0; } /** * @return difference between number of outgoing and ingoing edges * Note: may ONLY be called during shadowing */ public int getOutInDiff(){ return outShadowed.size() - inShadowed.size(); } /* (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(LayeredGraphNode other){ return shadowing ? compare(inShadowed, other.inShadowed) : compare(in, other.in); } /** * @param S * @param T * @return -1 (S < T) * 0 (S == T) * 1 (S > T) */ private int compare(LinkedList<LayeredGraphNode> S, LinkedList<LayeredGraphNode> T){ if(S.size() == 0 && T.size() == 0) return 0; if(S.size() == 0 && T.size() != 0) return -1; if(S.size() != 0 && T.size() == 0) return 1; // S.size() != 0 && T.size() != 0 int maxS = maxLabel(S); int maxT = maxLabel(T); if(maxS < 0) return 1; if(maxT < 0) return -1; if(maxS < maxT) return -1; if(maxS > maxT) return 1; LinkedList<LayeredGraphNode> SOut = new LinkedList<LayeredGraphNode>(); for(LayeredGraphNode g : S){ if(g.label != maxS) SOut.add(g); } LinkedList<LayeredGraphNode> TOut = new LinkedList<LayeredGraphNode>(); for(LayeredGraphNode g : T){ if(g.label != maxT) TOut.add(g); } return compare(SOut, TOut); } /* Methods for layering */ public LayeredGraphNode lowestIn(){ LayeredGraphNode low = null; for(LayeredGraphNode g : in){ if(low == null || low.layer < g.layer) low = g; } return low; } public LayeredGraphNode highestOut(){ LayeredGraphNode high = null; for(LayeredGraphNode g : out){ if(high == null || g.layer < high.layer) high = g; } return high; } public boolean AllInLabelled(){ for(LayeredGraphNode g : in){ //System.err.println("AllInLabelled: " + g.name + " label = " + g.label); if(g.label < 0) return false; } return true; } public boolean AllOutAssignedToLayers(){ //System.err.printf("AllOutAssignedToLayers %s\n", name); for(LayeredGraphNode g : out){ //System.err.println("\tconsidering " + g.name + " with layer " + g.layer + " and label " + g.label); if(g.layer < 0){ //System.err.printf("AllOutAssignedToLayers %s => false\n", name); return false; } } //System.err.printf("AllOutAssignedToLayers %s => true\n", name); return true; } public boolean AllOutAssignedToLayers(int layer){ //System.err.printf("AllOutAssignedToLayers %s, layer=%d", name, layer); for(LayeredGraphNode g : shadowing ? outShadowed : out){ //System.err.println("\tconsidering " + g.name + " with layer " + g.layer + " and label " + g.label); if(g.layer < 0 || (g.layer >= layer)){ //System.err.printf("AllOutAssignedToLayers %s => false\n", name); return false; } } //System.err.printf("AllOutAssignedToLayers %s => true\n", name); return true; } /* Methods for horizontal ordering */ /** * Get an ordered list of all nodes in layer that are directly connected to this node. * @param layer a horizontal layer of the graph * @return ordered list of nodes in layer directly connected to this node */ public LinkedList<LayeredGraphNode> getAllConnectedNeighbours(LinkedList<LayeredGraphNode> layer){ LinkedList<LayeredGraphNode> connected = new LinkedList<LayeredGraphNode>(); for(LayeredGraphNode g : layer){ if(in.contains(g) || out.contains(g)) connected.add(g); } return connected; } // /** // * @param layer // * @param k // * @return list of leftmost consecutive neighbors of this node that occur after index k in layer // */ // public LinkedList<LayeredGraphNode> getAllConnectedNeighboursAfter(LinkedList<LayeredGraphNode> layer, int k){ // LinkedList<LayeredGraphNode> connected = new LinkedList<LayeredGraphNode>(); // //boolean inSeq = false; // for(int i = k + 1; i < layer.size(); i++){ // LayeredGraphNode g = layer.get(i); // if(in.contains(g) || out.contains(g)){ // //inSeq = true; // connected.add(g); // } // //else if(inSeq) return connected; // } // return connected; // } // /** // * @param layer // * @param k // * @return list of rightmost consecutive neighbors of this node that occur before index k in layer // */ // public LinkedList<LayeredGraphNode> getAllConnectedNeighboursBefore(LinkedList<LayeredGraphNode> layer, int k){ // LinkedList<LayeredGraphNode> connected = new LinkedList<LayeredGraphNode>(); // //boolean inSeq = false; // for(int i = k - 1; i >= 0 ; i--){ // LayeredGraphNode g = layer.get(i); // if(in.contains(g) || out.contains(g)){ // //inSeq = true; // connected.addFirst(g); // } // //else if(inSeq) return connected; // } // return connected; // } public int rightMostConnectedNeighbour(){ int maxIn = -1; for(LayeredGraphNode g : in){ if(g.pos > maxIn) maxIn = g.pos; } return maxIn; } public boolean incidentToInnerSegment(LinkedList<LayeredGraphNode> other){ int minIn = INFINITY; int maxIn = -1; for(LayeredGraphNode g : in){ if(g.pos < minIn) minIn = g.pos; if(g.pos > maxIn) maxIn = g.pos; } for(LayeredGraphNode g1 : other){ if(g1.isVirtual()){ LayeredGraphNode g2 = g1.out.get(0); if(g2.isVirtual()) if(g1.pos < maxIn && g2.pos > pos || g1.pos > minIn && g2.pos < pos) return true; } } return false; } // public LayeredGraphNode virtualOutNeighbour(){ // for(LayeredGraphNode g : out) // if(g.isVirtual()) // return g; // return null; // } /* Methods for horizontal placement */ /** * Clear the data for the horizontal alignment computation. * Note that the values in xs[0] ... xs[1] are preserved */ public void clearHorizontal(){ root = align = this; setSink(this); x = -1; shift = DINFINITY; blockWidth = width(); } /** * Compute the average median of the four horizontal values that have been computed * for four alignment directions */ public void averageHorizontal(){ Arrays.sort(xs); x = (xs[1] + xs[2])/2; } /** * Get x value for given alignment. * @param dir a given alignment direction * @return The x value for that direction */ public double getX(Direction dir){ return xs[Direction.ord(dir)]; } /** * Set x value for given alignment * @param dir a given alignment direction * @param x the x value for that direction */ public void setX(Direction dir, double x){ this.x = xs[Direction.ord(dir)] = x; } public void setShift(double s){ shift = s; } public double getShift(){ return shift; } public void setSink(LayeredGraphNode s){ sink = s; } public LayeredGraphNode getSink(){ return sink; } public void shiftX(double shift[]){ for(Direction dir : Direction.dirs){ int k = Direction.ord(dir); xs[k] += shift[k]; } } /* Standard figure elements and operations */ public double figX(){ return x; } public double figY(){ return y; } double width(){ return figure != null ? figure.minSize.getX() : virtWidth; } double height(){ return figure != null ? figure.minSize.getY() : virtHeight; } String getLayer(){ return figure != null ? figure.prop.getStr(LAYER): ""; } public boolean mouseInside(Coordinate c) { if(figure!=null) return figure.mouseInside(c); else return false; } @Override public void resizeElement(Rectangle view) { localLocation.set(0, 0); if(figure != null){ figure.localLocation.set(x - figure.minSize.getX()/2, y - figure.minSize.getY()/2); } } @Override public void drawElement(GraphicsContext gc, List<IHasSWTElement> visibleSWTElements){ System.err.println("Drawing node " + name + ": " + figure); if(figure != null) figure.drawElement(gc, visibleSWTElements); } }