edu.uci.ics.jung.algorithms.layout.DAGLayout.java Source code

Java tutorial

Introduction

Here is the source code for edu.uci.ics.jung.algorithms.layout.DAGLayout.java

Source

/*
 * Copyright (c) 2008, the JUNG Project and the Regents of the University 
 * of California
 * All rights reserved.
 *
 * This software is open-source under the BSD license; see either
 * "license.txt" or
 * http://jung.sourceforge.net/license.txt for a description.
 */
/*
 * Created on 2008-06-26
 */
package edu.uci.ics.jung.algorithms.layout;

import static java.lang.Math.atan2;
import static java.lang.Math.sqrt;

import java.awt.Dimension;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections15.Transformer;
import org.apache.commons.collections15.map.LazyMap;

import edu.uci.ics.jung.algorithms.util.Context;
import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.EdgeIndexFunction;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.visualization.decorators.AbstractEdgeShapeTransformer;
import edu.uci.ics.jung.visualization.decorators.EdgeShape.IndexedRendering;

/**
 * DAGLayout is a layout algorithm which is suitable for tree-like directed
 * acyclic graphs. It will not accept cyclic graphs. The layout will result in
 * directed edges pointing downwards. Any vertices with no predecessors are
 * considered to be level 0, and will be fixed in the top of the layout. Any
 * vertex has a level one greater than the maximum level of all its successors.
 * 
 * It is an implementation of the algorithm in the book <it>Graph Drawing</it>,
 * by Giuseppe di Battista, Peter Eades, Roberto Tamassia and Ioannigs G.
 * Tollis.
 * 
 * It was created to work with relatively small graphs, so the algorithms here
 * are not optimized (they are not even of the best complexity class possible
 * for the problem). Also, recursion is used in some algorithms, so the depth of
 * the DAG is also somewhat limited (~8000 levels ? :-) ).
 * 
 * As this class uses virtual vertices to route the edges around the other
 * vertices, the EdgeShapeTransformer used must be aware of virtual vertices.
 * With this intent a BentLine class is nested inside this class.
 * 
 * FIXME: It does not accept parallel edges
 * 
 * FIXME: The layout does not take into account locked vertices.
 * 
 * FIXME: It is still not very good at minimizing edge crossings. The
 * orderVertice() method has a lot of room to improvements.
 * 
 * FIXME: The final layout does not take into account multi-graphs (unconnected
 * components), so when laid-out they will be somewhat entangled. This another
 * thing that can be improved in grapicalLayout().
 * 
 * FIXME: It would be good to include an EdgeShapeTransformer that draws curved
 * lines, instead of straight edges.
 * 
 * FIXME: It does not accept modification of the graph "on the fly", that is,
 * after the layout.
 * 
 * TODO: After a step of edge direction inversion, the same algorithm can be
 * used to layout cyclic graphs.
 * 
 * @author rstarr
 * @creation 2008-06-26
 * 
 */
public class DAGLayout<V extends Comparable<V>, E extends Comparable<E>> extends AbstractLayout<V, E> {

    /* Some basic layout restrictions */
    private static final int MINIMUM_SEPARATION_BETWEEN_LAYERS = 20;
    private static final int MINIMUM_SEPARATION_BETWEEN_VERTICES = 20;

    /* The final locations */
    protected Map<Virtualizable<V>, Point2D> locations = LazyMap.decorate(new HashMap<Virtualizable<V>, Point2D>(),
            new Transformer<Virtualizable<V>, Point2D>() {
                public Point2D transform(Virtualizable<V> arg0) {
                    return new Point2D.Double();
                }
            });

    /* A copy of the graph is needed so we can keep track of virtual edges */
    DirectedGraph<Virtualizable<V>, Virtualizable<E>> virtualGraph;

    /* Some other graph info */
    protected HashSet<Virtualizable<V>> roots = new HashSet<Virtualizable<V>>();

    /* From the root layer we can navigate to the others */
    protected Layer<V> rootLayer;

    /*
     * Associates the layer number to each edge. Used to fastly find the layer
     * number of a given edge.
     */
    protected HashMap<Virtualizable<V>, Integer> vertexLevel = new HashMap<Virtualizable<V>, Integer>();

    /*
     * Edges spanning more than two layer are broken in multiple edges
     * connecting virtual vertices. This list keeps track of the edges that
     * suffered this process, so the edge drawer can recover it to draw them
     * correctly.
     */
    protected HashMap<Virtualizable<E>, List<Virtualizable<V>>> virtualEdges = new HashMap<Virtualizable<E>, List<Virtualizable<V>>>();

    /**
     * @see edu.uci.ics.jung.algorithms.layout.AbstractLayout
     */
    public DAGLayout(Graph<V, E> graph) {
        super(graph, new Dimension(1, 1));
        initialize();
    }

    /**
     * @see edu.uci.ics.jung.algorithms.layout.AbstractLayout
     */
    public DAGLayout(Graph<V, E> graph, Transformer<V, Point2D> initializer) {
        super(graph, initializer);
        initialize();
    }

    /**
     * @see edu.uci.ics.jung.algorithms.layout.AbstractLayout
     */
    public DAGLayout(Graph<V, E> graph, Dimension size) {
        super(graph, size);
        initialize();
    }

    /**
     * @see edu.uci.ics.jung.algorithms.layout.AbstractLayout
     */
    public DAGLayout(Graph<V, E> graph, Transformer<V, Point2D> initializer, Dimension size) {
        super(graph, initializer, size);
        initialize();
    }

    /**
     * Carry on the tasks to prepare the layout. These include:
     * <ol>
     * <li>Creating a copy of the graph so we can include virtual vertices
     * <li>Find the roots of the graph
     * <li>Assign a layer to each vertex
     * <li>Create the virtual vertices for the edges spanning more than two
     * layers
     * <li>Order the vertices to reduce edge crossings
     * <li>Assign a real 2D position for each vertex
     * </ol>
     * 
     */
    public void initialize() {
        createVirtualGraph();
        findRoots();
        assignLayers();
        createVirtualVertices();
        orderVertices();
        graphicalLayout();
    }

    /**
     * Creates a graph that is a copy of the previous one, but that allows for
     * the inclusion of virtual vertices.
     */
    protected void createVirtualGraph() {
        this.virtualGraph = new DirectedSparseGraph<Virtualizable<V>, Virtualizable<E>>();
        Graph<V, E> graph = getGraph();
        Pair<V> pair;

        for (V v : graph.getVertices()) {
            this.virtualGraph.addVertex(new Virtualizable<V>(v));
        }
        for (E e : graph.getEdges()) {
            pair = graph.getEndpoints(e);
            this.virtualGraph.addEdge(new Virtualizable<E>(e), new Virtualizable<V>(pair.getFirst()),
                    new Virtualizable<V>(pair.getSecond()));
        }
    }

    /**
     * Calculate each root of the graph. A root is any vertex of the graph that
     * has no incoming vertex.
     */
    protected void findRoots() {
        for (Virtualizable<V> v : this.virtualGraph.getVertices()) {
            Collection<Virtualizable<V>> predecessors = this.virtualGraph.getPredecessors(v);
            if (predecessors.size() == 0) {
                this.roots.add(v);
            }
        }
        if (this.roots.size() == 0) {
            throw new Error("This graph is not acyclic.");
        }
    }

    /**
     * For each of the roots found in the graph, distribute the following
     * vertices in the layers, using a maximu depth approach.
     * 
     */
    protected void assignLayers() {

        this.rootLayer = new Layer<V>((DirectedGraph<Virtualizable<V>, ?>) this.virtualGraph, null, null);
        for (Virtualizable<V> root : this.roots) {
            assignLayers(root, rootLayer);
        }
    }

    /**
     * A recursive method for allocating the layer for each vertex. Recursively
     * scans the graph, in a depth first manner, assigning each vertex to a
     * deeper layer.
     * 
     * @param v
     *            The current vertex
     * @param layer
     *            The level of the current vertex
     * @param this.vertexLevel A list of all the vertex that have already been
     *        visited
     */
    protected void assignLayers(Virtualizable<V> v, Layer<V> layer) {

        int previousLayerOfNode;
        int thisLevel = layer.getLevel();
        layer.add(v);
        this.vertexLevel.put(v, thisLevel);

        for (Virtualizable<V> child : this.virtualGraph.getSuccessors(v)) {
            if (this.vertexLevel.containsKey(child)) {
                previousLayerOfNode = this.vertexLevel.get(child);
                if (thisLevel >= previousLayerOfNode) {
                    layer.getLayerOnLevel(previousLayerOfNode).remove(child);
                    this.vertexLevel.remove(child);
                    // When calling assignLayers again, all the descendants of
                    // this node will be reassigned
                    // FIXME: This algorithm is not efficient at all!
                } else {
                    /*
                     * We stop the iteration if the child is already in a level
                     * bigger than this one
                     */
                    return;
                }
            }
            assignLayers(child, layer.getNextLayer(true));
        }
    }

    /**
     * In the layout of a layered digraph it is interesting that no vertices
     * span more than one layer. So, to avoid this situation, we add virtual
     * vertices. These will in fact be the restrictions to the path of the arcs
     * so they can go around the nodes in intermediate layers.
     * 
     * This method scans every edge and, for each one that spans more than two
     * layers, adds the virtual nodes.
     */
    protected void createVirtualVertices() {

        // Create a copy of the old edges.
        Collection<Virtualizable<E>> oldEdges = new LinkedList<Virtualizable<E>>(this.virtualGraph.getEdges());

        // This will hold a list of the virtual vertices of an edge
        LinkedList<Virtualizable<V>> list;
        Pair<Virtualizable<V>> pair;
        Virtualizable<V> first, second, virtual;
        int layerFirst, layerSecond;

        for (Virtualizable<E> e : oldEdges) {
            pair = this.virtualGraph.getEndpoints(e);
            first = pair.getFirst();
            second = pair.getSecond();
            layerFirst = this.vertexLevel.get(first);
            layerSecond = this.vertexLevel.get(second);

            if (layerSecond - layerFirst > 1) {
                list = new LinkedList<Virtualizable<V>>();
                this.virtualEdges.put(e, list);

                // We know that second is always under first
                while (layerSecond - layerFirst > 1) {
                    virtual = new Virtualizable<V>(null, true);
                    list.add(virtual);

                    this.rootLayer.getLayerOnLevel(layerFirst + 1).add(virtual);
                    this.vertexLevel.put(virtual, layerFirst + 1);

                    this.virtualGraph.removeEdge(e);
                    this.virtualGraph.addEdge(new Virtualizable<E>(null, true), first, virtual);
                    this.virtualGraph.addEdge(new Virtualizable<E>(null, true), virtual, second);

                    // Now we change the elements so we keep breaking the edges
                    // longer than one layer
                    layerFirst += 1;
                    first = virtual;
                    e = this.virtualGraph.findEdge(first, second);
                }
            }

        }
    }

    /**
     * Sweeps each layer (but the first) and order the vertices
     */
    protected void orderVertices() {
        Layer<V> currentLayer = this.rootLayer.getNextLayer(false);
        while (currentLayer != null) {
            orderVertices(currentLayer);
            currentLayer = currentLayer.getNextLayer(false);
        }
    }

    /**
     * Order the vertices in this layer using a layer-by-layer sweep and an
     * adjacent-exchange algorithm. Repeats the ordering until the total
     * crossing number for the layer does not reduce any further.
     */
    protected void orderVertices(Layer<V> layer) {

        // No need to order only one vertex
        if (layer.size() < 2) {
            return;
        }

        boolean crossingChanged = true;
        Virtualizable<V> u, v;
        List<Virtualizable<V>> oldOrdering;
        Iterator<Virtualizable<V>> it;

        while (crossingChanged) {
            crossingChanged = false;
            oldOrdering = new LinkedList<Virtualizable<V>>(layer.getVertices());

            it = oldOrdering.iterator();
            u = it.next();
            while (it.hasNext()) {
                v = it.next();
                if (layer.crossings(u, v) > layer.crossings(v, u)) {
                    layer.exchange(u, v);
                    crossingChanged = true;
                }
                u = v; // Step to the next node
            }
        }
    }

    /**
     * Assign a 2D position for each node. This position is based on the
     * requested dimensions of the graph respecting a minimum separation between
     * nodes and layers and trying to distribute the layers and the vertices
     * evenly across the drawing area.
     * 
     * FIXME: This layout will not give a good layout for multi-graphs, because
     * it does not consider each multi-graph as an separate entity.
     * 
     */
    protected void graphicalLayout() {
        int layerNumber = 0;
        int vertexNumber = 0;

        double separationBetweenVertices;
        double separationBetweenLayers = this.getSize().height / (this.rootLayer.getRemainingLevels() + 1);
        double start = -separationBetweenLayers / 2;

        if (separationBetweenLayers < MINIMUM_SEPARATION_BETWEEN_LAYERS) {
            separationBetweenLayers = MINIMUM_SEPARATION_BETWEEN_LAYERS;
        }

        Layer<V> currentLayer = this.rootLayer;
        while (currentLayer != null) {
            layerNumber++;
            separationBetweenVertices = this.getSize().width / (currentLayer.size() + 1);
            if (separationBetweenVertices < MINIMUM_SEPARATION_BETWEEN_VERTICES) {
                separationBetweenVertices = MINIMUM_SEPARATION_BETWEEN_VERTICES;
            }

            vertexNumber = 0;
            for (Virtualizable<V> v : currentLayer.getVertices()) {
                vertexNumber++;
                setLocation(v, vertexNumber * separationBetweenVertices,
                        layerNumber * separationBetweenLayers + start);
            }
            currentLayer = currentLayer.getNextLayer(false);
        }

    }

    /**
     * Returns the Coordinates object that stores the vertex' x and y location.
     * 
     * @param v
     *            A Vertex that is a part of the Graph being visualized.
     * @return A Coordinates object with x and y locations.
     */
    protected Point2D getCoordinates(V v) {
        if (v != null) {
            return getCoordinates(new Virtualizable<V>(v));
        } else {
            return new Point2D.Double();
        }
    }

    /**
     * Returns the Coordinates object that stores the vertex' x and y location.
     * 
     * @param v
     *            A Vertex that is a part of the Graph being visualized.
     * @return A Coordinates object with x and y locations.
     */
    protected Point2D getCoordinates(Virtualizable<V> v) {
        return locations.get(v);
    }

    /**
     * Returns the x coordinate of the vertex from the Coordinates object. in
     * most cases you will be better off calling getLocation(Vertex v);
     * 
     * @see edu.uci.ics.jung.algorithms.layout.Layout#getX(edu.uci.ics.jung.graph.Vertex)
     */
    public double getX(V v) {
        assert getCoordinates(v) != null : "Cannot getX for an unmapped vertex " + v;
        return getCoordinates(v).getX();
    }

    /**
     * Returns the y coordinate of the vertex from the Coordinates object. In
     * most cases you will be better off calling getLocation(Vertex v)
     * 
     * @see edu.uci.ics.jung.algorithms.layout.Layout#getX(edu.uci.ics.jung.graph.Vertex)
     */
    public double getY(V v) {
        assert getCoordinates(v) != null : "Cannot getY for an unmapped vertex " + v;
        return getCoordinates(v).getY();
    }

    /**
     * Returns the location of a given vertex.
     * 
     * @param v
     *            a Vertex of interest
     * @return the location point of the supplied vertex
     */
    public Point2D getLocation(V v) {
        return getCoordinates(v);
    }

    /**
     * Forcibly moves a vertex to the (x,y) location by setting its x and y
     * locations to the inputted location. Does not add the vertex to the
     * "dontmove" list, and (in the default implementation) does not make any
     * adjustments to the rest of the graph.
     */
    public void setLocation(V picked, double x, double y) {
        Point2D coord = getCoordinates(picked);
        coord.setLocation(x, y);
    }

    public void setLocation(V picked, Point2D p) {
        Point2D coord = getCoordinates(picked);
        coord.setLocation(p);
    }

    public void setLocation(Virtualizable<V> picked, double x, double y) {
        Point2D coord = getCoordinates(picked);
        coord.setLocation(x, y);
    }

    public Point2D transform(V v) {
        return getCoordinates(v);
    }

    /**
     * @see edu.uci.ics.jung.algorithms.layout.Layout#reset()
     */
    public void reset() {
    }

    /**
     * Draws an edge that is a series of straight lines connecting the control
     * virtual points that make an edge. This is necessary if the edges should
     * route through the vertices in the intermediary layers.
     */
    public static class BentLine<V extends Comparable<V>, E extends Comparable<E>>
            extends AbstractEdgeShapeTransformer<V, E> implements IndexedRendering<V, E> {

        /* Singleton instance of the BentLine shape */
        private static GeneralPath instance = new GeneralPath();

        /* Indexing of parallel edges */
        protected EdgeIndexFunction<V, E> parallelEdgeIndexFunction;

        /* The layout will be used to get the virtual vertex information */
        protected DAGLayout<V, E> layout;

        /**
         * Will produce edges for the given DAGLayout.
         * 
         * @param layout
         */
        public BentLine(DAGLayout<V, E> layout) {
            this.layout = layout;
        }

        public void setEdgeIndexFunction(EdgeIndexFunction<V, E> parallelEdgeIndexFunction) {
            this.parallelEdgeIndexFunction = parallelEdgeIndexFunction;
        }

        /**
         * @return the parallelEdgeIndexFunction
         */
        public EdgeIndexFunction<V, E> getEdgeIndexFunction() {
            return parallelEdgeIndexFunction;
        }

        /**
         * Creates the edge, routing it through all the virtual vertices
         * associated to that edge.
         */
        public Shape transform(Context<Graph<V, E>, E> context) {
            Virtualizable<E> e = new Virtualizable<E>(context.element);

            if (this.layout.virtualEdges.containsKey(e)) {

                Point2D coord, firstPoint, lastPoint;

                Pair<V> pair = context.graph.getEndpoints(context.element);
                Virtualizable<V> first = new Virtualizable<V>(pair.getFirst());
                Virtualizable<V> second = new Virtualizable<V>(pair.getSecond());
                AffineTransform translation = new AffineTransform();
                AffineTransform transform = new AffineTransform();

                instance.reset();
                // Adds the path coordinates (in the standard coordinate frame)
                firstPoint = this.layout.getCoordinates(first);
                instance.moveTo(0.0f, 0.0f);
                translation.setToTranslation(-firstPoint.getX(), -firstPoint.getY());

                // Adds the coordinates of the virtual vertices
                for (Virtualizable<V> v : this.layout.virtualEdges.get(e)) {
                    coord = translation.transform(this.layout.getCoordinates(v), null);
                    instance.lineTo((float) coord.getX(), (float) coord.getY());
                }
                lastPoint = translation.transform(this.layout.getCoordinates(second), null);
                instance.lineTo((float) lastPoint.getX(), (float) lastPoint.getY());
                // finish

                /*
                 * Now we need to scale the line according to the position of
                 * the virtual vertices.
                 */
                double x2 = lastPoint.getX();
                double y2 = lastPoint.getY();

                double theta = -atan2(y2, x2);
                double scale = 1 / sqrt(x2 * x2 + y2 * y2);

                transform.setToScale(scale, 1.0d);
                transform.rotate(theta);
                instance.transform(transform);
                for (Virtualizable<V> v : this.layout.virtualEdges.get(e)) {
                    coord = translation.transform(this.layout.getCoordinates(v), null);
                }

            } else {
                // This edge does not go through any virtual vertex
                instance.reset();
                instance.moveTo(0.0f, 0.0f);
                instance.lineTo(1.0f, 1.0f);
            }

            return instance;
        }
    }
}

/**
 * This class can wrap a type and be used when virtual objects of that type must
 * be created. Its usefulness is mainly connected to the ability to create
 * virtual vertices and edges while laying out a graph. It is used both for
 * vertices and edges.
 * 
 * @author rstarr
 * 
 * @param <V>
 *            The class that will be wrapped.
 */
class Virtualizable<V extends Comparable<V>> implements Comparable<Virtualizable<V>> {

    // This is used to generate an individual id for each virtual node
    static long virtualIdGenerator = 0;

    V v;
    boolean virtual = false;

    // When this node is virtual, this method will be used to compare for
    // equality.
    long virtualId;

    public Virtualizable(V v) {
        this.v = v;
    }

    public Virtualizable(V v, boolean virtual) {
        this.virtual = virtual;
        this.v = v;
        // Generate an unique id for this vertex
        this.virtualId = Virtualizable.virtualIdGenerator++;
    }

    public String toString() {
        if (this.virtual) {
            return "<_:" + this.virtualId + ">";
        } else {
            return "<" + v.toString() + ">";
        }
    }

    public boolean equals(Object o) {
        if (o instanceof Virtualizable) {
            Virtualizable<?> other = (Virtualizable<?>) o;
            if (this.virtual) {
                if (other.virtual) {
                    return this.virtualId == other.virtualId;
                } else {
                    return false; // Virtual vertices are ALWAYS different
                    // from non-virtual ones
                }
            } else {
                return this.v.equals(other.v);
            }
        }
        return false;
    }

    /**
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    public int compareTo(Virtualizable<V> o) {
        if (this.virtual) {
            if (o.virtual) {
                return (new Long(this.virtualId)).compareTo(o.virtualId);
            } else {
                // A virtual node is always smaller than a non-virtual one.
                return -1;
            }
        } else {
            return this.v.compareTo(o.v);
        }
    }

    @Override
    public int hashCode() {
        if (this.virtual) {
            // Unique hash for each virtual node
            return this.toString().hashCode();
        } else {
            return this.v.hashCode();
        }
    }
}

/**
 * Represents a layer in the layering of a DAG. It provides for the exchange of
 * position between two vertices in the layer, to calculate the degree of
 * crossing between two vertices and to find the layer directly before and
 * after.
 * 
 * @author RSTARR
 * 
 */
class Layer<V extends Comparable<V>> {
    private Layer<V> previousLayer;
    private Layer<V> nextLayer;
    private LinkedList<Virtualizable<V>> vertices = new LinkedList<Virtualizable<V>>();
    private DirectedGraph<Virtualizable<V>, ?> graph;

    public Layer(DirectedGraph<Virtualizable<V>, ?> graph, Layer<V> previousLayer, Layer<V> nextLayer) {
        this.graph = graph;
        this.previousLayer = previousLayer;
        this.nextLayer = nextLayer;
    }

    /**
     * @param previousLayerOfNode
     * @return
     */
    protected Layer<V> getLayerOnLevel(int layer) {
        if (getLevel() == layer) {
            return this;
        } else if (getLevel() > layer && this.previousLayer != null) {
            return this.previousLayer.getLayerOnLevel(layer);
        } else if (getLevel() < layer && this.nextLayer != null) {
            return this.nextLayer.getLayerOnLevel(layer);
        } else {
            return null;
        }
    }

    /**
     * @return
     */
    public int size() {
        return this.vertices.size();
    }

    /**
     * True if there is no layer before this one
     * 
     * @return
     */
    public boolean isFirstLayer() {
        return this.previousLayer == null;
    }

    /**
     * True if there is no layer after this one.
     * 
     * @return
     */
    public boolean isLastLayer() {
        return this.nextLayer == null;
    }

    /**
     * Returns the next layer. If the create argument is true and this layer is
     * the last layer, a new layer will be created and returned.
     * 
     * @param create
     *            If true, a new layer will be created if this is the last
     *            layer.
     * @return The next layer, or null if this is the last layer and create is
     *         <code>false</code>.
     */
    public Layer<V> getNextLayer(boolean create) {
        if (isLastLayer() && create) {
            this.nextLayer = new Layer<V>(this.graph, this, null);
        }
        return this.nextLayer;
    }

    /**
     * Returns the previous layer. If the create argument is true and this layer
     * is the first layer, a new layer will be created and returned.
     * 
     * @param create
     *            If true, a new layer will be created if this is the first
     *            layer.
     * @return The previous layer, or null if this is the first layer and create
     *         is <code>false</code>.
     */
    public Layer<V> getPreviousLayer(boolean create) {
        if (isFirstLayer() && create) {
            this.previousLayer = new Layer<V>(this.graph, null, this);
        }
        return this.previousLayer;
    }

    /**
     * Return the level in which this layer is. The first layer has level 0.
     * 
     * @return The level in which this layer is.
     */
    public int getLevel() {
        if (isFirstLayer()) {
            return 0;
        } else {
            return 1 + this.previousLayer.getLevel();
        }
    }

    /**
     * Return the number of levels separating this layer from the last layer.
     */
    public int getRemainingLevels() {
        if (isLastLayer()) {
            return 0;
        } else {
            return 1 + this.nextLayer.getRemainingLevels();
        }
    }

    /**
     * Get an ordered collection of the vertices in this layer
     * 
     * @return
     */
    public List<Virtualizable<V>> getVertices() {
        return this.vertices;
    }

    /**
     * Add a vertex as the last vertex of this layer
     * 
     * @param v
     */
    public void add(Virtualizable<V> v) {
        this.vertices.add(v);
    }

    /**
     * Add a vertex as the last vertex of this layer
     * 
     * @param v
     */
    public void remove(Virtualizable<V> v) {
        this.vertices.remove(v);
    }

    /**
     * Exchange the position of two vertices u and v.
     * 
     * @param u
     * @param v
     */
    public void exchange(Virtualizable<V> u, Virtualizable<V> v) {
        int posU = this.vertices.indexOf(u);
        int posV = this.vertices.indexOf(v);

        this.vertices.set(posV, u);
        this.vertices.set(posU, v);
    }

    /**
     * Return the number of vertices that arrive in u that are crossed by
     * vertices arriving in v. If this is the first layer, returns always 0.
     * 
     * @param u
     * @param v
     * @return The number of crossings
     */
    public int crossings(final Virtualizable<V> u, final Virtualizable<V> v) {
        if (isFirstLayer()) {
            return 0;
        } else {
            int posU = this.vertices.indexOf(u);
            int posV = this.vertices.indexOf(v);

            if (posU == posV) {
                return 0;
            } else {
                int crossing = 0;

                Collection<Virtualizable<V>> inVerticesU = this.graph.getPredecessors(u);
                Collection<Virtualizable<V>> inVerticesV = this.graph.getPredecessors(v);

                for (Virtualizable<V> w : inVerticesU) {
                    for (Virtualizable<V> z : inVerticesV) {
                        if (this.previousLayer.getPos(z) < this.previousLayer.getPos(w)) {
                            crossing++;
                        }
                    }
                }
                return crossing;
            }
        }
    }

    private int getPos(Virtualizable<V> v) {
        return this.vertices.indexOf(v);
    }
}