edu.purdue.cc.bionet.ui.GraphVisualizer.java Source code

Java tutorial

Introduction

Here is the source code for edu.purdue.cc.bionet.ui.GraphVisualizer.java

Source

/*
    
This file is part of BioNet.
    
BioNet is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
BioNet is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with BioNet.  If not, see <http://www.gnu.org/licenses/>.
    
*/

package edu.purdue.cc.bionet.ui;

import edu.purdue.cc.bionet.ui.layout.*;

import java.util.Collection;
import java.util.LinkedList;
import java.util.Vector;
import java.util.HashMap;
import java.util.List;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.geom.Point2D;
import java.awt.event.MouseWheelEvent;
import java.awt.event.ItemListener;
import java.awt.event.ItemEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.event.ComponentListener;
import java.awt.event.ComponentEvent;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.event.InputEvent;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Color;
import java.awt.Paint;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JViewport;

import edu.uci.ics.jung.algorithms.layout.*;
import edu.uci.ics.jung.algorithms.layout.AbstractLayout;
import edu.uci.ics.jung.algorithms.shortestpath.DijkstraShortestPath;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.UndirectedSparseGraph;
import edu.uci.ics.jung.graph.util.*;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.visualization.*;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.control.*;
import edu.uci.ics.jung.visualization.control.AbsoluteCrossoverScalingControl;
import edu.uci.ics.jung.visualization.control.ViewScalingControl;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.layout.ObservableCachingLayout;
import edu.uci.ics.jung.visualization.picking.PickedState;
import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer;
import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;

import org.apache.commons.collections15.Transformer;
import org.apache.commons.collections15.CollectionUtils;
import org.apache.log4j.Logger;

/**
 * A class for visualizing a network graph. 
 */
public class GraphVisualizer<V, E> extends VisualizationViewer<V, E>
        implements Graph<V, E>, ItemListener, MouseListener, Scalable {
    protected Graph<V, E> graph = new UndirectedSparseGraph<V, E>();
    private LayoutAnimator layoutAnimator;
    private Thread AnimThread;
    private AbsoluteCrossoverScalingControl absoluteViewScaler = new AbsoluteCrossoverScalingControl();
    protected JScrollPane scrollPane;
    protected float currentZoom = 0.99f;
    protected static float minimumZoom = 0.99f;
    private DijkstraShortestPath<V, E> dijkstra;

    private LinkedList<PickedStateChangeListener<V>> pickedVertexStateChangeListeners = new LinkedList<PickedStateChangeListener<V>>();
    private LinkedList<PickedStateChangeListener<E>> pickedEdgeStateChangeListeners = new LinkedList<PickedStateChangeListener<E>>();
    private LinkedList<GraphItemChangeListener<V>> vertexChangeListeners = new LinkedList<GraphItemChangeListener<V>>();
    private LinkedList<GraphItemChangeListener<E>> edgeChangeListeners = new LinkedList<GraphItemChangeListener<E>>();
    private LinkedList<ChangeListener> animationListeners = new LinkedList<ChangeListener>();
    private LinkedList<GraphMouseListener<E>> graphMouseEdgeListeners = new LinkedList<GraphMouseListener<E>>();
    protected Paint vertexPaint = Color.ORANGE;
    protected Paint vertexOutline = Color.ORANGE;
    protected Paint pickedVertexPaint = Color.YELLOW;
    protected Paint pickedVertexOutline = Color.YELLOW;
    protected Paint edgePaint = Color.GREEN;
    protected Paint pickedEdgePaint = Color.BLACK;
    protected Color pickedLabelColor = Color.BLUE;
    protected Paint commonNeighborPaint = new Color(1.0f, 0.3f, 0.3f);
    protected Paint commonNeighborOutline = new Color(1.0f, 0.3f, 0.3f);
    protected boolean commonNeighborIndicator;
    protected NeighborCollection<V, E> commonNeighbors;
    private static final long repaintDelay = 1000L;

    /**
     * Constructs a GraphVisualizer object.
     */
    public GraphVisualizer() {
        this(CircleLayout.class);
    }

    /**
     * Constructs a GraphVisualizer Object.
     * 
     * @param layout A Class object of the type of class to use for the graph layout.
     */
    public GraphVisualizer(Class<? extends AbstractLayout> layout) {
        super(GraphVisualizer.getLayoutInstance(layout));
        this.setup();
    }

    /**
     * Constructs a GraphVisualizer Object.
     * 
     * @param layout A Class object of the type of class to use for the graph layout.
     * @param size The initial size of the GraphVisualizer Object.
     */
    public GraphVisualizer(Class<? extends AbstractLayout> layout, Dimension size) {
        super(GraphVisualizer.getLayoutInstance(layout), size);
        this.setup();
    }

    // override the inherited constructors
    /**
     * Constructs a GraphVisualizer Object.
     * 
     * @param layout The Layout to use for the graph.
     */
    public GraphVisualizer(Layout<V, E> layout) {
        super(layout);
        this.setup();
    }

    /**
     * Constructs a GraphVisualizer Object.
     * 
     * @param layout The Layout to use for the graph.
     * @param preferredSize The preferred size of the GraphVisualizer.
     */
    public GraphVisualizer(Layout<V, E> layout, Dimension preferredSize) {
        super(layout, preferredSize);
        this.setup();
    }

    /**
     * Constructs a GraphVisualizer Object.
     * 
     * @param model The VisualizationModel to use for the graph.
     */
    public GraphVisualizer(VisualizationModel<V, E> model) {
        super(model);
        this.setup();
    }

    /**
     * Constructs a GraphVisualizer Object.
     * 
     * @param model The VisualizationModel to use for the graph.
     * @param preferredSize The preferred size of the graph.
     */
    public GraphVisualizer(VisualizationModel<V, E> model, Dimension preferredSize) {
        super(model, preferredSize);
        this.setup();
    }

    /**
     * Creates a new layout instance from a Class Object.
     * 
     * @param layout The Class object to create the instance of.
     * @return A new instance of the Layout.
     */
    protected static Layout getLayoutInstance(Class<? extends AbstractLayout> layout) {
        return GraphVisualizer.getLayoutInstance(layout, new UndirectedSparseGraph());
    }

    /**
     * Creates a Layout instance from a Class Object.
     * 
     * @param layout The class object to create the instance of.
     * @param graph The graph to use to instantiate the Layout.
     * @return A new instance of the Layout.
     */
    protected static Layout getLayoutInstance(Class<? extends AbstractLayout> layout, Graph graph) {
        try {
            return layout.getConstructor(Graph.class).newInstance(graph);

        } catch (NoSuchMethodException e) {
            Logger.getLogger(GraphVisualizer.class).error(e);
        } catch (InstantiationException e) {
            Logger.getLogger(GraphVisualizer.class).error(e);
        } catch (IllegalAccessException e) {
            Logger.getLogger(GraphVisualizer.class).error(e);
        } catch (java.lang.reflect.InvocationTargetException e) {
            Logger.getLogger(GraphVisualizer.class).error(e);
        }
        return null;
    }

    /**
     * Performs the necessary actions to set up the Graph.
     */
    protected void setup() {
        this.graph = (UndirectedSparseGraph<V, E>) this.getGraphLayout().getGraph();
        this.getRenderContext().setVertexLabelTransformer(new ToStringLabeller<V>());
        //      this.getRenderContext( ).setEdgeLabelTransformer( new ToStringLabeller<E>( ));
        this.getRenderer().getVertexLabelRenderer().setPosition(Position.CNTR);
        PluggableGraphMouse mouse = new PluggableGraphMouse() {
            public void mouseWheelMoved(MouseWheelEvent e) {
                scale((float) Math.pow(1.25, -e.getWheelRotation()), e.getPoint());
            }
        };
        mouse.add(new PickingGraphMousePlugin());
        mouse.add(new PickingGraphMousePlugin(-1, InputEvent.BUTTON1_MASK | InputEvent.CTRL_MASK));
        this.setGraphMouse(mouse);
        this.getPickedVertexState().addItemListener(this);
        this.getPickedEdgeState().addItemListener(this);
        this.addMouseListener(this);
        this.addComponentListener(new LayoutScaler());

        // set up coloring
        Transformer v = new Transformer<V, Paint>() {
            public Paint transform(V v) {
                if (getPickedVertexState().isPicked(v)) {
                    return pickedVertexPaint;
                } else {
                    if (commonNeighborIndicator && isCommonNeighbor(v)) {
                        return commonNeighborPaint;
                    } else {
                        return vertexPaint;
                    }
                }
            }
        };
        Transformer vo = new Transformer<V, Paint>() {
            public Paint transform(V v) {
                if (getPickedVertexState().isPicked(v)) {
                    return pickedVertexOutline;
                } else {
                    if (commonNeighborIndicator && isCommonNeighbor(v)) {
                        return commonNeighborOutline;
                    } else {
                        return vertexOutline;
                    }
                }
            }
        };

        this.getRenderContext().setVertexFillPaintTransformer(v);
        this.getRenderContext().setVertexDrawPaintTransformer(vo);

        Transformer e = new Transformer<E, Paint>() {
            public Paint transform(E e) {
                if (getPickedEdgeState().isPicked(e)) {
                    return pickedEdgePaint;
                } else {
                    return edgePaint;
                }
            }
        };
        this.getRenderContext().setEdgeDrawPaintTransformer(e);
        this.setPickedLabelColor(this.pickedLabelColor);
        this.commonNeighbors = new NeighborCollection<V, E>(this);
    }

    /**
     * Returns the underlying graph for this GraphVisualizer.
     * 
     * @return The underlying graph for this GraphVisualizer.
     */
    public Graph getGraph() {
        return this.graph;
    }

    /**
     * Sets a new Layout for the graph.
     * 
     * @param layout A Class object containing the Layout to be used.
     */
    public void setGraphLayout(Class<? extends AbstractLayout> layout) {
        this.setGraphLayout((Layout<V, E>) GraphVisualizer.getLayoutInstance(layout, this.graph));
    }

    /**
     * Sets a new Layout for the graph.
     * 
     * @param layout The Layout instance to use for the new Graph Layout.
     */
    public void setGraphLayout(Layout<V, E> layout) {
        super.setGraphLayout(layout);
        layout.initialize();
        this.graph = (UndirectedSparseGraph<V, E>) this.getGraphLayout().getGraph();
    }

    /**
     * Starts the animation of the graph.
     */
    public void animate() {
        this.animate(true);
    }

    /**
     * Starts the animation of the graph.
     * 
     * @param enable True to stop animation of the Graph, false to stop.
     */
    public void animate(boolean enable) {
        if (this.layoutAnimator != null)
            this.layoutAnimator.stop();
        if (enable) {
            this.layoutAnimator = new FRLayoutAnimator(this.getGraphLayout());
            for (ChangeListener c : animationListeners) {
                this.layoutAnimator.addAnimationListener(c);
            }
            this.AnimThread = new Thread(this.layoutAnimator);
            this.AnimThread.start();
        }
    }

    /**
     * Adds a listener to the graph to listen for starts/stops of animation.
     * 
     * @param c The ChangeListener to be notified when animation is 
     *   started/stopped.
     */
    public void addAnimationListener(ChangeListener c) {
        this.animationListeners.add(c);
        if (this.layoutAnimator != null) {
            this.layoutAnimator.addAnimationListener(c);
        }
    }

    /**
     * Resets the graph Layout to it's initial state.
     */
    public void resetLayout() {
        this.getGraphLayout().reset();
    }

    /**
     * Sets all Graph nodes as selected.
     */
    public void selectAll() {
        PickedState<V> state = this.getPickedVertexState();
        Collection<V> vertices = this.graph.getVertices();
        for (V v : vertices) {
            state.pick(v, true);
        }

        PickedState<E> edgeState = this.getPickedEdgeState();
        Collection<E> edges = this.graph.getEdges();
        for (E e : edges) {
            edgeState.pick(e, true);
        }
    }

    /**
     * Clear the selection of all nodes.
     */
    public void clearSelection() {
        PickedState<V> state = this.getPickedVertexState();
        Collection<V> pickedVertices = new Vector<V>(state.getPicked());
        for (V v : pickedVertices) {
            state.pick(v, false);
        }
        PickedState<E> edgeState = this.getPickedEdgeState();
        Collection<E> pickedEdges = new Vector<E>(edgeState.getPicked());
        for (E e : pickedEdges) {
            edgeState.pick(e, false);
        }
    }

    /**
     * Scales the graph view by the given amount.
     * 
     * @param amount The multiplier to apply to the scaling.
     * @return The new zoom level.
     */
    public float scale(float amount) {
        return this.scale(amount, this.getCenterPoint());
    }

    /**
     * Scales the graph view by the givem amount, centered on the 
     * given point.
     * 
     * @param amount The multiplier to apply to the scaling.
     * @param center The center point for the scaling operation.
     * @return The new zoom level.
     */
    public float scale(float amount, Point2D center) {
        return this.scaleTo(currentZoom * amount, center);
    }

    /**
     * Scales to the given graph level, 1.0 being 100%.
     * 
     * @param level The level to zoom to.
     * @return The new zoom level.
     */
    public float scaleTo(float level) {
        return this.scaleTo(level, this.getCenterPoint());
    }

    /**
     * Scales to the given zoom level, 1.0 being 100%, centered on the 
     * given point.
     * 
     * @param level The level to zoom to.
     * @param center The center point for the scaling operation.
     * @return The new zoom level.
     */
    public float scaleTo(float level, Point2D center) {
        if (this.scrollPane == null)
            return 1.0f;
        float oldZoom = this.currentZoom;
        this.currentZoom = Math.max(minimumZoom, level);
        //      this.absoluteViewScaler.scale( this, level, center );
        Dimension viewSize;
        if (level < 1.0f) {
            viewSize = this.scrollPane.getSize();
        } else {
            viewSize = this.scrollPane.getViewport().getExtentSize();
        }
        Dimension newSize = new Dimension((int) (viewSize.width * currentZoom),
                (int) (viewSize.height * currentZoom));
        this.setPreferredSize(newSize);
        this.setSize(newSize);
        //      new LayoutScaler( this.getGraphLayout( )).setSize( newSize );
        if (Float.compare(level, 1.0f) <= 0)
            this.center();

        // translate the new view position so the mouse is in the same place
        // on the scaled view.
        JViewport vp = this.scrollPane.getViewport();
        double centerX = center.getX();
        double centerY = center.getY();
        double viewPortMouseX = centerX - vp.getViewPosition().getX();
        double viewPortMouseY = centerY - vp.getViewPosition().getY();
        centerX *= currentZoom / oldZoom;
        centerY *= currentZoom / oldZoom;
        viewPortMouseX = centerX - viewPortMouseX;
        viewPortMouseY = centerY - viewPortMouseY;
        vp.setViewPosition(new Point((int) viewPortMouseX, (int) viewPortMouseY));

        return this.currentZoom;
    }

    // JUNGs built in layout scaling doesn't seem to work very well,
    // so here's my workaround implementation.
    /**
     * A class for scaling a Layout externally.
     */
    private class LayoutScaler implements ComponentListener {
        private AbstractLayout<V, E> layout;
        private ObservableCachingLayout<V, E> observableLayout;
        private int layoutInitialized = 0;

        /**
         * Creates a new LayoutScaler
         * 
         * @param layout The layout to be manipulated.
         */
        public LayoutScaler() {
            super();
        }

        /**
         * Changes the size of the underlying Layout.
         * 
         * @param size The new size for the layout.
         */
        private void setSize(Dimension size) {
            ObservableCachingLayout<V, E> observableLayout = (ObservableCachingLayout) getGraphLayout();
            Layout<V, E> tmpLayout = observableLayout;
            while (!AbstractLayout.class.isAssignableFrom(tmpLayout.getClass()))
                tmpLayout = ((LayoutDecorator<V, E>) tmpLayout).getDelegate();
            AbstractLayout<V, E> layout = (AbstractLayout<V, E>) tmpLayout;

            // the first time the graph is resized, re-initialize the layout to make sure it gets
            // the right size.  Any other time, just scale it. The first resize should be when the 
            // graph is made visible and laid out.
            // this is kind of a hack; there may be a better way to handle this.
            // I tried listening for componentShown, but it didn't work properly.
            if (layoutInitialized < 1) {
                layout.getSize().setSize(size);
                layout.initialize();
                layoutInitialized++;
                return;
            }

            // change the size of the layout without triggering the automatic resizing.
            double wScale = size.getWidth() / layout.getSize().getWidth();
            double hScale = size.getHeight() / layout.getSize().getHeight();
            double scale = Math.min(wScale, hScale);
            layout.getSize().setSize(size);
            Collection<V> vertices = new Vector(getVertices());
            synchronized (graph) {
                for (V v : vertices) {
                    double x = layout.getX(v) * scale; //Math.min( size.getWidth( ) - 10, Math.max( 10, layout.getX( v ) * scale ));
                    double y = layout.getY(v) * scale; //Math.min( size.getHeight( ) - 10, Math.max( 10, layout.getY( v ) * scale ));
                    layout.setLocation(v, new Point2D.Double(x, y));
                }
            }
            // alert the ObservableLayout that things have changed.
            observableLayout.fireStateChanged();
        }

        // ComponentListener interface methods
        public void componentHidden(ComponentEvent e) {
        }

        public void componentMoved(ComponentEvent e) {
        }

        public void componentResized(ComponentEvent e) {
            Dimension newSize = ((Scalable) e.getComponent()).getScrollPane().getSize();
            newSize.width *= currentZoom;
            newSize.height *= currentZoom;
            this.setSize(newSize);
        }

        public void componentShown(ComponentEvent e) {
        }
    }

    /**
     * Centers the graph in the display.
     */
    public void center() {
        // toggle the scrollbars back and forth to center the image
        JScrollBar sb = scrollPane.getHorizontalScrollBar();
        sb.setValue(sb.getMaximum());
        sb.setValue(sb.getMinimum());
        sb.setValue((sb.getMaximum() + sb.getMinimum()) / 2);
        sb = scrollPane.getVerticalScrollBar();
        sb.setValue(sb.getMaximum());
        sb.setValue(sb.getMinimum());
        sb.setValue((sb.getMaximum() + sb.getMinimum()) / 2);
    }

    /**
     * Gets the ScrollPane associated with this viewer.
     * 
     * @return A ScrollPane which contains this GraphVisualizer.
     */
    public JScrollPane getScrollPane() {
        if (this.scrollPane == null) {
            this.scrollPane = new JScrollPane(this);
            this.scrollPane.setWheelScrollingEnabled(false);
        }
        return this.scrollPane;
    }

    /**
     * Get the center point for the graph.
     * 
     * @return The center point of this graph as a Point2D.
     */
    public Point2D getCenterPoint() {
        Dimension size = this.getGraphLayout().getSize();
        return new Point2D.Double(size.width / 2.0, size.height / 2.0);
    }

    /**
     * Sets the Paint to be used for filling vertices on the graph.
     * 
     * @param p The new Paint to use.
     */
    public void setVertexPaint(Paint p) {
        this.vertexPaint = p;
    }

    /**
     * Gets the current paint that is being used to render vertices.
     * 
     * @return The Paint currently being used to render vertices.
     */
    public Paint getVertexPaint() {
        return this.vertexPaint;
    }

    /**
     * Sets the Paint to be used for outlining vertices on the graph.
     * 
     * @param p The new Paint to use.
     */
    public void setVertexOutline(Paint p) {
        this.vertexOutline = p;
    }

    /**
     * Gets the current paint that is being used to render vertices.
     * 
     * @return The Paint currently being used to render vertices.
     */
    public Paint getVertexOutline() {
        return this.vertexOutline;
    }

    /**
     * Sets the paint to use on picked vertices.
     * 
     * @param p The Paint to use.
     */
    public void setPickedVertexPaint(Paint p) {
        this.pickedVertexPaint = p;
    }

    /**
     * Gets the Paint being used on picked vertices.
     * 
     * @return The Paint being used.
     */
    public Paint getPickedVertexPaint() {
        return this.pickedVertexPaint;
    }

    /**
     * Sets the paint to use to outline picked vertices.
     * 
     * @param p The Paint to use.
     */
    public void setPickedVertexOutline(Paint p) {
        this.pickedVertexOutline = p;
    }

    /**
     * Gest the paint being used to outline picked vertices.
     * 
     * @return The Paint being used.
     */
    public Paint getPickedVertexOutline() {
        return this.pickedVertexOutline;
    }

    /**
     * Returns a Collection of vertices which are common neighbors to
     * the entire selection.
     * 
     * @return The common neighbors.
     */
    public Collection<V> getCommonNeighbors() {
        return commonNeighbors;
    }

    /**
     * Tells whether a given vertex is a common neighbor of the entire selection.
     * 
     * @param v The vertex to check.
     * @return true if the vertex has a connection to each node in the selection, 
     * false if not.
     */
    public boolean isCommonNeighbor(V v) {
        return this.commonNeighbors.contains(v);
    }

    /**
     * Sets the flag of whether to indicate common neighbors or not.
     * 
     * @param state The new state of the flag.
     */
    public void setIndicateCommonNeighbors(boolean state) {
        this.commonNeighborIndicator = state;
    }

    /**
     * Gets the flag of whether common neighbors are currently being indicated
     * on the graph.
     * 
     * @return true or false.
     */
    public boolean getIndicateCommonNeighbors() {
        return this.commonNeighborIndicator;
    }

    /**
     * Sets the paint to use on nodes that are neighbors of all selected nodes.
     * 
     * @param p The Paint to use.
     */
    public void setCommonNeighborPaint(Paint p) {
        this.commonNeighborPaint = p;
    }

    /**
     * Gets the Paint being used to indicate commone neighbors of all selected
     * nodes.
     * 
     * @return The Paint being used.
     */
    public Paint getCommonNeighborPaint() {
        return this.commonNeighborPaint;
    }

    /**
     * Sets the paint to use on edges.
     * 
     * @param p The Paint to use.
     */
    public void setEdgePaint(Paint p) {
        this.edgePaint = p;
    }

    /**
     * Gets the Paint being used on edges.
     * 
     * @return The Paint being used.
     */
    public Paint getEdgePaint() {
        return this.edgePaint;
    }

    /**
     * Sets the Paint to be used on picked edges.
     * 
     * @param p The Paint to use.
     */
    public void setPickedEdgePaint(Paint p) {
        this.pickedEdgePaint = p;
    }

    /**
     * Gets the Paint being used on picked edges.
     * 
     * @return The Paint being used.
     */
    public Paint getPickedEdgePaint() {
        return this.pickedEdgePaint;
    }

    /**
     * Sets the Color to be used on picked vertex labels.
     * 
     * @param c The color to use.
     */
    public void setPickedLabelColor(Color c) {
        this.pickedLabelColor = c;
        this.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(c));
    }

    /**
     * Gets the Color being used on picked vertex labels.
     * 
     * @return The Color being used.
     */
    public Color getPickedLabelColor() {
        return this.pickedLabelColor;
    }

    /**
     * Adds a GraphMouseListener to listen for edge clicks on the graph.
     * 
     * @param l The GraphMouseListener to be added.
     */
    public void addGraphMouseEdgeListener(GraphMouseListener<E> l) {
        this.graphMouseEdgeListeners.add(l);
    }

    /**
     * Notifies all GraphMouseListeners listening for Edge clicks that an edge has
     * been clicked on.
     * 
     * @param edge The edge that was clicked on.
     * @param event The MouseEvent which triggered this action.
     */
    private void fireGraphMouseEdgeClickedEvent(E edge, MouseEvent event) {
        for (GraphMouseListener<E> g : graphMouseEdgeListeners) {
            g.graphClicked(edge, event);
        }
    }

    /**
     * Notifies all GraphMouseListeners listening for Edge clicks that an edge has
     * been clicked on.
     * 
     * @param edge The edge that was clicked on.
     * @param event The MouseEvent which triggered this action.
     */
    private void fireGraphMouseEdgePressedEvent(E edge, MouseEvent event) {
        for (GraphMouseListener<E> g : graphMouseEdgeListeners) {
            g.graphPressed(edge, event);
        }
    }

    /**
     * Notifies all GraphMouseListeners listening for Edge clicks that an edge has
     * been clicked on.
     * 
     * @param edge The edge that was clicked on.
     * @param event The MouseEvent which triggered this action.
     */
    private void fireGraphMouseEdgeReleasedEvent(E edge, MouseEvent event) {
        for (GraphMouseListener<E> g : graphMouseEdgeListeners) {
            g.graphReleased(edge, event);
        }
    }

    /**
     * Adds a PickedVertexStateChangeListener to the graph.
     * 
     * @param l The PickedStateChangeListener to add.
     */
    public void addPickedVertexStateChangeListener(PickedStateChangeListener<V> l) {
        this.pickedVertexStateChangeListeners.add(l);
    }

    /**
     * Adds a PickedEdgeStateChangeListener to the graph.
     * 
     * @param l The PickedStateChangeListener to add.
     */
    public void addPickedEdgeStateChangeListener(PickedStateChangeListener<E> l) {
        this.pickedEdgeStateChangeListeners.add(l);
    }

    /**
     * Fires a PickedStateChangeEvent for a change on a vertex.
     * 
     * @param item The item whose state changed.
     * @param picked Whether or not the item is selected.
     */
    private void firePickedVertexChangeEvent(V item, boolean picked) {
        PickedStateChangeEvent<V> event = new PickedStateChangeEvent<V>(this, item, picked);
        for (PickedStateChangeListener<V> p : this.pickedVertexStateChangeListeners) {
            p.stateChanged(event);
        }
    }

    /**
     * Fires a PickedStateChangeEvent for a change on an Edge.
     * 
     * @param item The item whose state changed.
     * @param picked Whether or not the item is selected.
     */
    private void firePickedEdgeChangeEvent(E item, boolean picked) {
        PickedStateChangeEvent<E> event = new PickedStateChangeEvent<E>(this, item, picked);
        for (PickedStateChangeListener<E> p : this.pickedEdgeStateChangeListeners) {
            p.stateChanged(event);
        }
    }

    /**
     * Adds a listener to listen for the adding or removal of vertices.
     * 
     * @param g The GraphItemChangeListener to add.
     */
    public void addVertexChangeListener(GraphItemChangeListener<V> g) {
        this.vertexChangeListeners.add(g);
    }

    /**
     * Adds a listener to listen for the adding or removal of edges.
     * 
     * @param g the GraphItemChangeListener to add.
     */
    public void addEdgeChangeListener(GraphItemChangeListener<E> g) {
        this.edgeChangeListeners.add(g);
    }

    /**
     * Fires an event to notify GraphItemChangeListeners of the adding/removal of
     * a vertex.
     * 
     * @param item The vertex which was added or removed.
     * @param action The action which was performed. One of 
     *   GraphItemChangeEvent.ADDED or GraphItemChangeEvent.REMOVED
     */
    private void fireVertexChangeEvent(V item, int action) {
        GraphItemChangeEvent<V> event = new GraphItemChangeEvent<V>(this, item, action);
        for (GraphItemChangeListener<V> v : vertexChangeListeners) {
            v.stateChanged(event);
        }
    }

    /**
     * Fires an event to notify GraphItemChangeListeners of the adding/removal of
     * an edge.
     * 
     * @param item The edge which was added or removed.
     * @param action The action which was performed. One of 
     *   GraphItemChangeEvent.ADDED or GraphItemChangeEvent.REMOVED
     */
    private void fireEdgeChangeEvent(E item, int action) {
        GraphItemChangeEvent<E> event = new GraphItemChangeEvent<E>(this, item, action);
        for (GraphItemChangeListener<E> e : edgeChangeListeners)
            e.stateChanged(event);
    }

    /**
     * The itemStateChanged method of the ItemListener interface.
     * 
     * @param event The event which triggered this ItemListener.
     */
    public void itemStateChanged(ItemEvent event) {
        Object source = event.getItem();
        try {
            firePickedVertexChangeEvent((V) source, event.getStateChange() == ItemEvent.SELECTED);
        } catch (ClassCastException e) {
            firePickedEdgeChangeEvent((E) source, event.getStateChange() == ItemEvent.SELECTED);
        }

    }

    // ** MouseListener interface methods **

    /**
     * The mouseClicked method of the MouseListener interface.
     * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
     * 
     * @param event The event which triggered this action.
     */
    public void mouseClicked(MouseEvent event) {
        E edge = this.getPickSupport().getEdge(this.getGraphLayout(), event.getX(), event.getY());
        V vertex = this.getPickSupport().getVertex(this.getGraphLayout(), event.getX(), event.getY());
        if (edge != null && vertex == null) {
            this.fireGraphMouseEdgeClickedEvent(edge, event);
        }
    }

    /**
     * The mousePressed method of the MouseListener interface.
     * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
     * 
     * @param event The event which triggered this action.
     */
    public void mousePressed(MouseEvent event) {
        E edge = this.getPickSupport().getEdge(this.getGraphLayout(), event.getX(), event.getY());
        V vertex = this.getPickSupport().getVertex(this.getGraphLayout(), event.getX(), event.getY());
        if (edge != null && vertex == null) {
            this.fireGraphMouseEdgePressedEvent(edge, event);
        }
    }

    /**
     * The mouseReleased method of the MouseListener interface.
     * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
     * 
     * @param event The event which triggered this action.
     */
    public void mouseReleased(MouseEvent event) {
        E edge = this.getPickSupport().getEdge(this.getGraphLayout(), event.getX(), event.getY());
        V vertex = this.getPickSupport().getVertex(this.getGraphLayout(), event.getX(), event.getY());
        if (edge != null && vertex == null) {
            this.fireGraphMouseEdgeReleasedEvent(edge, event);
        }
    }

    /**
     * The mouseEntered method of the MouseListener interface. Not implemented.
     * 
     * @param event The event which triggered this action.
     */
    public void mouseEntered(MouseEvent event) {
    }

    /**
     * The mouseExited method of the MouseListener interface. Not implemented.
     * 
     * @param event The event which triggered this action.
     */
    public void mouseExited(MouseEvent event) {
    }

    /**
     * Returns a List containing the nodes of the shortest path between v1 and v2,
     * not including v1. The size of this List will be the length of the path.
     * 
     * @param v1 The node to start at.
     * @param v2 The node to end with.
     * @return A List containing the shortest path, or null if no such path exists.
     */
    public List<E> getShortestPath(V v1, V v2) {
        if (dijkstra == null) {
            // create a new DijkstraShortestPath
            dijkstra = new DijkstraShortestPath<V, E>(this);
            // listen for edge changes and reset DijkstraShortestPath
            this.addEdgeChangeListener(new GraphItemChangeListener<E>() {
                public void stateChanged(GraphItemChangeEvent e) {
                    dijkstra.reset();

                }
            });
        }
        try {
            return dijkstra.getPath(v1, v2);
        } catch (NullPointerException e) {
            return null;
        }
    }

    // *** Experimental stuff for performance ***
    //   @Override
    //   protected void paintComponent( Graphics g ) {
    //      if ( !this.getIgnoreRepaint( )) {
    //         super.paintComponent( g );
    //      }
    //   }
    //
    //   @Override
    //   public void paint( Graphics g ) {
    //      if ( !this.getIgnoreRepaint( )) {
    //         super.paint( g );
    //      }
    //   }
    //
    //   @Override
    //   public void paintAll( Graphics g ) {
    //      if ( !this.getIgnoreRepaint( )) {
    //         super.paintAll( g );
    //      }
    //   }
    //
    //   @Override
    //   public void repaint( ) {
    //      if ( !this.getIgnoreRepaint( )) {
    //         super.repaint( );
    //      }
    //   }
    //
    //   @Override
    //   public void update( Graphics g ) {
    //      if ( !this.getIgnoreRepaint( )) {
    //         super.update( g );
    //      }
    //   }
    // *** End experimental stuff ***

    // Graph interface Methods
    public boolean addEdge(E e, V v1, V v2) {
        boolean returnValue = this.graph.addEdge(e, v1, v2);
        this.repaint(repaintDelay);
        return returnValue;
    }

    public boolean addEdge(E e, V v1, V v2, EdgeType edgetype) {
        boolean returnValue = this.graph.addEdge(e, v1, v2, edgetype);
        this.repaint(repaintDelay);
        return returnValue;
    }

    public V getDest(E directed_edge) {
        return this.graph.getDest(directed_edge);
    }

    public Pair<V> getEndpoints(E edge) {
        return this.graph.getEndpoints(edge);
    }

    public Collection<E> getInEdges(V vertex) {
        return this.graph.getInEdges(vertex);
    }

    public V getOpposite(V vertex, E edge) {
        return this.graph.getOpposite(vertex, edge);
    }

    public Collection<E> getOutEdges(V vertex) {
        return this.graph.getOutEdges(vertex);
    }

    public int getPredecessorCount(V vertex) {
        return this.graph.getPredecessorCount(vertex);
    }

    public Collection<V> getPredecessors(V vertex) {
        return this.graph.getPredecessors(vertex);
    }

    public V getSource(E directed_edge) {
        return this.graph.getSource(directed_edge);
    }

    public int getSuccessorCount(V vertex) {
        return this.graph.getSuccessorCount(vertex);
    }

    public Collection<V> getSuccessors(V vertex) {
        return this.graph.getSuccessors(vertex);
    }

    public int inDegree(V vertex) {
        return this.graph.inDegree(vertex);
    }

    public boolean isDest(V vertex, E edge) {
        return this.graph.isDest(vertex, edge);
    }

    public boolean isPredecessor(V v1, V v2) {
        return this.graph.isPredecessor(v1, v2);
    }

    public boolean isSource(V vertex, E edge) {
        return this.graph.isSource(vertex, edge);
    }

    public boolean isSuccessor(V v1, V v2) {
        return this.graph.isSuccessor(v1, v2);
    }

    public int outDegree(V vertex) {
        return this.graph.outDegree(vertex);
    }

    // Hypergraph interface methods
    public boolean addEdge(E edge, Collection<? extends V> vertices) {
        boolean returnValue = this.graph.addEdge(edge, vertices);
        this.repaint(repaintDelay);
        this.fireEdgeChangeEvent(edge, GraphItemChangeEvent.ADDED);
        return returnValue;
    }

    public boolean addEdge(E edge, Collection<? extends V> vertices, EdgeType edge_type) {
        boolean returnValue = this.graph.addEdge(edge, vertices, edge_type);
        this.repaint(repaintDelay);
        this.fireEdgeChangeEvent(edge, GraphItemChangeEvent.ADDED);
        return returnValue;
    }

    public boolean addVertex(V vertex) {
        this.fireVertexChangeEvent(vertex, GraphItemChangeEvent.ADDED);
        boolean returnValue = this.graph.addVertex(vertex);
        this.repaint(repaintDelay);
        return returnValue;
    }

    public boolean containsEdge(E edge) {
        return this.graph.containsEdge(edge);
    }

    public boolean containsVertex(V vertex) {
        return this.graph.containsVertex(vertex);
    }

    public E findEdge(V v1, V v2) {
        return this.graph.findEdge(v1, v2);
    }

    public Collection<E> findEdgeSet(V v1, V v2) {
        return this.graph.findEdgeSet(v1, v2);
    }

    public int getEdgeCount() {
        return this.graph.getEdgeCount();
    }

    public int getEdgeCount(EdgeType edge_type) {
        return this.graph.getEdgeCount(edge_type);
    }

    public Collection<E> getEdges() {
        return this.graph.getEdges();
    }

    public Collection<E> getEdges(EdgeType edge_type) {
        return this.graph.getEdges(edge_type);
    }

    public int degree(V vertex) {
        return this.graph.degree(vertex);
    }

    public EdgeType getDefaultEdgeType() {
        return this.graph.getDefaultEdgeType();
    }

    public EdgeType getEdgeType(E edge) {
        return this.graph.getEdgeType(edge);
    }

    public int getIncidentCount(E edge) {
        return this.graph.getIncidentCount(edge);
    }

    public Collection<E> getIncidentEdges(V vertex) {
        return this.graph.getIncidentEdges(vertex);
    }

    public Collection<V> getIncidentVertices(E edge) {
        return this.graph.getIncidentVertices(edge);
    }

    public int getNeighborCount(V vertex) {
        return this.graph.getNeighborCount(vertex);
    }

    public Collection<V> getNeighbors(V vertex) {
        return this.graph.getNeighbors(vertex);
    }

    public int getVertexCount() {
        return this.graph.getVertexCount();
    }

    public Collection<V> getVertices() {
        return this.graph.getVertices();
    }

    public boolean isIncident(V vertex, E edge) {
        return this.graph.isIncident(vertex, edge);
    }

    public boolean isNeighbor(V v1, V v2) {
        return this.graph.isNeighbor(v1, v2);
    }

    public boolean removeEdge(E edge) {
        this.fireEdgeChangeEvent(edge, GraphItemChangeEvent.REMOVED);
        boolean returnValue = this.graph.removeEdge(edge);
        this.repaint(repaintDelay);
        return returnValue;
    }

    public boolean removeVertex(V vertex) {
        this.fireVertexChangeEvent(vertex, GraphItemChangeEvent.REMOVED);
        boolean returnValue = this.graph.removeVertex(vertex);
        this.repaint(repaintDelay);
        return returnValue;
    }

    //UndirectedSparseGraph pass through Methods
    public boolean addEdge(E edge, Pair<? extends V> endpoints, EdgeType edgeType) {
        this.fireEdgeChangeEvent(edge, GraphItemChangeEvent.ADDED);
        boolean returnValue = this.graph.addEdge(edge, endpoints, edgeType);
        this.repaint(repaintDelay);
        return returnValue;
    }

    private class NeighborCollection<V, E> extends LinkedList<V>
            implements PickedStateChangeListener<V>, GraphItemChangeListener<E> {
        GraphVisualizer graph;

        public NeighborCollection(GraphVisualizer graph) {
            this.graph = graph;
            graph.addPickedVertexStateChangeListener(this);
            graph.addEdgeChangeListener(this);
        }

        public void stateChanged(GraphItemChangeEvent<E> event) {
            this.update();
        }

        public void stateChanged(PickedStateChangeEvent<V> event) {
            // check to see if a vertex was added to or taken away from the selection.
            // if one was added, we can improve performance by simply removing nodes 
            // from the NeighborCollection which are not neighbors of the new node.
            if (event.getStateChange()) {
                if (graph.getPickedVertexState().getPicked().size() == 1) {
                    Collection newNeighbors = graph.getNeighbors(event.getItem());
                    if (newNeighbors != null)
                        this.addAll(newNeighbors);
                } else {
                    for (V v : new Vector<V>(this)) {
                        this.remove(event.getItem());
                        try {
                            if (!graph.isNeighbor(event.getItem(), v))
                                this.remove(v);
                        } catch (IllegalArgumentException e) {
                        }
                    }
                }
            } else {
                this.update();
            }
        }

        private void update() {
            Collection<V> selected = new Vector<V>(this.graph.getPickedVertexState().getPicked());
            // if nothing is selected, this Collection should also contain nothing.
            if (selected.size() == 0) {
                this.clear();
                return;
            }
            // Basically here we add all neighbors of the first selected node, then
            // remove any nodes which are not neighbors of each selected node until
            // we have gone through the entire selection.
            Collection<V> neighbors;
            boolean firstPass = true;
            for (V v : selected) {
                if (firstPass) {
                    if (!this.contains(v)) {
                        this.addAll(graph.getNeighbors(v));
                    }
                    firstPass = false;
                }
                this.remove(v);
                neighbors = graph.getNeighbors(v);
                for (V u : new Vector<V>(this)) {
                    if (!neighbors.contains(u)) {
                        this.remove(u);
                    }
                }
            }
        }
    }

}