[med-svn] [libjung-java] 02/08: New upstream version 2.1.1

Andreas Tille tille at debian.org
Thu Dec 14 17:31:41 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository libjung-java.

commit 5fa88fc299b1860c6aa8c121fb58730d53238a87
Author: Andreas Tille <tille at debian.org>
Date:   Thu Dec 14 18:10:11 2017 +0100

    New upstream version 2.1.1
---
 .gitignore                                         |   25 +
 .travis.yml                                        |   35 +
 AUTHORS                                            |   19 +
 CONTRIBUTING.md                                    |   35 +
 LICENSE                                            |   41 +
 README.md                                          |   52 +
 RELEASE.md                                         |  229 ++++
 jung-algorithms-2.0.1-sources.jar                  |  Bin 208091 -> 0 bytes
 jung-algorithms/assembly.xml                       |   19 +
 jung-algorithms/pom.xml                            |   32 +
 .../blockmodel/StructurallyEquivalent.java         |  174 +++
 .../algorithms/blockmodel/VertexPartition.java     |  131 +++
 .../ics/jung/algorithms/blockmodel/package.html    |   32 +
 .../algorithms/cluster/BicomponentClusterer.java   |  165 +++
 .../cluster/EdgeBetweennessClusterer.java          |  109 ++
 .../jung/algorithms/cluster/VoltageClusterer.java  |  370 ++++++
 .../algorithms/cluster/WeakComponentClusterer.java |   73 ++
 .../uci/ics/jung/algorithms/cluster/package.html   |   34 +
 .../algorithms/filters/EdgePredicateFilter.java    |   70 ++
 .../uci/ics/jung/algorithms/filters/Filter.java    |   26 +
 .../ics/jung/algorithms/filters/FilterUtils.java   |  100 ++
 .../algorithms/filters/KNeighborhoodFilter.java    |  142 +++
 .../algorithms/filters/VertexPredicateFilter.java  |   75 ++
 .../uci/ics/jung/algorithms/filters/package.html   |   30 +
 .../jung/algorithms/flows/EdmondsKarpMaxFlow.java  |  314 +++++
 .../edu/uci/ics/jung/algorithms/flows/package.html |   19 +
 .../generators/EvolvingGraphGenerator.java         |   31 +
 .../jung/algorithms/generators/GraphGenerator.java |   21 +
 .../algorithms/generators/Lattice2DGenerator.java  |  196 ++++
 .../ics/jung/algorithms/generators/package.html    |   19 +
 .../generators/random/BarabasiAlbertGenerator.java |  293 +++++
 .../random/EppsteinPowerLawGenerator.java          |  128 +++
 .../generators/random/ErdosRenyiGenerator.java     |  105 ++
 .../random/KleinbergSmallWorldGenerator.java       |  192 ++++
 .../random/MixedRandomGraphGenerator.java          |   80 ++
 .../jung/algorithms/generators/random/package.html |   27 +
 .../jung/algorithms/importance/AbstractRanker.java |  392 +++++++
 .../importance/BetweennessCentrality.java          |  189 +++
 .../jung/algorithms/importance/KStepMarkov.java    |  135 +++
 .../ics/jung/algorithms/importance/Ranking.java    |   79 ++
 .../importance/RelativeAuthorityRanker.java        |   73 ++
 .../algorithms/importance/WeightedNIPaths.java     |  196 ++++
 .../ics/jung/algorithms/layout/AbstractLayout.java |  292 +++++
 .../jung/algorithms/layout/AggregateLayout.java    |  250 ++++
 .../ics/jung/algorithms/layout/BalloonLayout.java  |  145 +++
 .../ics/jung/algorithms/layout/CircleLayout.java   |  148 +++
 .../uci/ics/jung/algorithms/layout/DAGLayout.java  |  340 ++++++
 .../uci/ics/jung/algorithms/layout/FRLayout.java   |  318 ++++++
 .../uci/ics/jung/algorithms/layout/FRLayout2.java  |  317 ++++++
 .../algorithms/layout/GraphElementAccessor.java    |   49 +
 .../uci/ics/jung/algorithms/layout/ISOMLayout.java |  225 ++++
 .../uci/ics/jung/algorithms/layout/KKLayout.java   |  417 +++++++
 .../edu/uci/ics/jung/algorithms/layout/Layout.java |   87 ++
 .../jung/algorithms/layout/LayoutDecorator.java    |  107 ++
 .../uci/ics/jung/algorithms/layout/PolarPoint.java |  106 ++
 .../jung/algorithms/layout/RadialTreeLayout.java   |  116 ++
 .../layout/RadiusGraphElementAccessor.java         |  192 ++++
 .../ics/jung/algorithms/layout/SpringLayout.java   |  330 ++++++
 .../ics/jung/algorithms/layout/SpringLayout2.java  |  139 +++
 .../ics/jung/algorithms/layout/StaticLayout.java   |   49 +
 .../uci/ics/jung/algorithms/layout/TreeLayout.java |  252 ++++
 .../uci/ics/jung/algorithms/layout/package.html    |   39 +
 .../layout/util/RandomLocationTransformer.java     |   62 +
 .../ics/jung/algorithms/layout/util/Relaxer.java   |   43 +
 .../ics/jung/algorithms/layout/util/VisRunner.java |  146 +++
 .../ics/jung/algorithms/layout/util/package.html   |   20 +
 .../uci/ics/jung/algorithms/metrics/Metrics.java   |   74 ++
 .../jung/algorithms/metrics/StructuralHoles.java   |  343 ++++++
 .../ics/jung/algorithms/metrics/TriadicCensus.java |  209 ++++
 .../uci/ics/jung/algorithms/metrics/package.html   |   26 +
 .../java/edu/uci/ics/jung/algorithms/package.html  |   42 +
 .../scoring/AbstractIterativeScorer.java           |  370 ++++++
 .../scoring/AbstractIterativeScorerWithPriors.java |  117 ++
 .../jung/algorithms/scoring/BarycenterScorer.java  |   55 +
 .../algorithms/scoring/BetweennessCentrality.java  |  350 ++++++
 .../algorithms/scoring/ClosenessCentrality.java    |   55 +
 .../ics/jung/algorithms/scoring/DegreeScorer.java  |   45 +
 .../scoring/DistanceCentralityScorer.java          |  249 ++++
 .../ics/jung/algorithms/scoring/EdgeScorer.java    |   28 +
 .../algorithms/scoring/EigenvectorCentrality.java  |   52 +
 .../edu/uci/ics/jung/algorithms/scoring/HITS.java  |  147 +++
 .../jung/algorithms/scoring/HITSWithPriors.java    |  199 ++++
 .../ics/jung/algorithms/scoring/KStepMarkov.java   |  157 +++
 .../uci/ics/jung/algorithms/scoring/PageRank.java  |   70 ++
 .../algorithms/scoring/PageRankWithPriors.java     |  142 +++
 .../ics/jung/algorithms/scoring/VertexScorer.java  |   28 +
 .../ics/jung/algorithms/scoring/VoltageScorer.java |  250 ++++
 .../uci/ics/jung/algorithms/scoring/package.html   |   42 +
 .../scoring/util/DelegateToEdgeTransformer.java    |   48 +
 .../jung/algorithms/scoring/util/ScoringUtils.java |   72 ++
 .../scoring/util/UniformDegreeWeight.java          |   55 +
 .../jung/algorithms/scoring/util/UniformInOut.java |   53 +
 .../ics/jung/algorithms/scoring/util/VEPair.java   |   56 +
 .../scoring/util/VertexScoreTransformer.java       |   46 +
 .../ics/jung/algorithms/scoring/util/package.html  |   34 +
 .../shortestpath/BFSDistanceLabeler.java           |  170 +++
 .../algorithms/shortestpath/DijkstraDistance.java  |  545 +++++++++
 .../shortestpath/DijkstraShortestPath.java         |  297 +++++
 .../ics/jung/algorithms/shortestpath/Distance.java |   46 +
 .../shortestpath/DistanceStatistics.java           |  165 +++
 .../shortestpath/MinimumSpanningForest.java        |  163 +++
 .../shortestpath/MinimumSpanningForest2.java       |  106 ++
 .../shortestpath/PrimMinimumSpanningTree.java      |  118 ++
 .../jung/algorithms/shortestpath/ShortestPath.java |   31 +
 .../algorithms/shortestpath/ShortestPathUtils.java |   62 +
 .../shortestpath/UnweightedShortestPath.java       |  152 +++
 .../ics/jung/algorithms/shortestpath/package.html  |   30 +
 .../transformation/DirectionTransformer.java       |  125 ++
 .../transformation/FoldingTransformer.java         |  325 ++++++
 .../transformation/VertexPartitionCollapser.java   |  103 ++
 .../jung/algorithms/transformation/package.html    |   28 +
 .../ics/jung/algorithms/util/BasicMapEntry.java    |   81 ++
 .../jung/algorithms/util/DiscreteDistribution.java |  219 ++++
 .../edu/uci/ics/jung/algorithms/util/Indexer.java  |   56 +
 .../ics/jung/algorithms/util/IterativeContext.java |   28 +
 .../ics/jung/algorithms/util/IterativeProcess.java |  174 +++
 .../ics/jung/algorithms/util/KMeansClusterer.java  |  257 +++++
 .../ics/jung/algorithms/util/MapBinaryHeap.java    |  379 +++++++
 .../algorithms/util/MapSettableTransformer.java    |   45 +
 .../algorithms/util/SelfLoopEdgePredicate.java     |   23 +
 .../jung/algorithms/util/SettableTransformer.java  |   31 +
 .../ics/jung/algorithms/util/WeightedChoice.java   |  203 ++++
 .../edu/uci/ics/jung/algorithms/util/package.html  |   31 +
 jung-algorithms/src/site/site.xml                  |   14 +
 .../cluster/TestBicomponentClusterer.java          |  268 +++++
 .../cluster/TestEdgeBetweennessClusterer.java      |   83 ++
 .../cluster/WeakComponentClustererTest.java        |   19 +
 .../filters/impl/TestKNeighborhoodFilter.java      |   62 +
 .../algorithms/flows/TestEdmondsKarpMaxFlow.java   |  211 ++++
 .../jung/algorithms/generators/TestLattice2D.java  |   87 ++
 .../generators/random/TestBarabasiAlbert.java      |  254 +++++
 .../random/TestEppsteinPowerLawGenerator.java      |  106 ++
 .../generators/random/TestErdosRenyi.java          |   71 ++
 .../generators/random/TestKleinberg.java           |   39 +
 .../importance/TestBetweennessCentrality.java      |  119 ++
 .../algorithms/importance/TestKStepMarkov.java     |   86 ++
 .../algorithms/importance/TestWeightedNIPaths.java |   85 ++
 .../ics/jung/algorithms/layout/FRLayout2Test.java  |   31 +
 .../ics/jung/algorithms/layout/FRLayoutTest.java   |   31 +
 .../uci/ics/jung/algorithms/metrics/TestTriad.java |  166 +++
 .../scoring/TestBetweennessCentrality.java         |  138 +++
 .../uci/ics/jung/algorithms/scoring/TestHITS.java  |  119 ++
 .../algorithms/scoring/TestHITSWithPriors.java     |   77 ++
 .../jung/algorithms/scoring/TestKStepMarkov.java   |   70 ++
 .../ics/jung/algorithms/scoring/TestPageRank.java  |   80 ++
 .../algorithms/scoring/TestPageRankWithPriors.java |   91 ++
 .../jung/algorithms/scoring/TestVoltageScore.java  |   79 ++
 .../shortestpath/TestBFSDistanceLabeler.java       |   67 ++
 .../shortestpath/TestPrimMinimumSpanningTree.java  |   62 +
 .../algorithms/shortestpath/TestShortestPath.java  |  528 +++++++++
 .../shortestpath/TestUnweightedShortestPath.java   |   95 ++
 .../uci/ics/jung/algorithms/util/NotRandom.java    |   60 +
 .../jung/algorithms/util/TestWeightedChoice.java   |   78 ++
 jung-api-2.0.1-sources.jar                         |  Bin 32291 -> 0 bytes
 jung-api/assembly.xml                              |   19 +
 jung-api/pom.xml                                   |   40 +
 .../java/edu/uci/ics/jung/graph/DirectedGraph.java |   24 +
 .../main/java/edu/uci/ics/jung/graph/Forest.java   |   96 ++
 .../main/java/edu/uci/ics/jung/graph/Graph.java    |  247 ++++
 .../edu/uci/ics/jung/graph/GraphDecorator.java     |  332 ++++++
 .../java/edu/uci/ics/jung/graph/Hypergraph.java    |  442 ++++++++
 .../java/edu/uci/ics/jung/graph/KPartiteGraph.java |   40 +
 .../java/edu/uci/ics/jung/graph/MultiGraph.java    |   20 +
 .../edu/uci/ics/jung/graph/ObservableGraph.java    |  142 +++
 .../src/main/java/edu/uci/ics/jung/graph/Tree.java |   52 +
 .../edu/uci/ics/jung/graph/UndirectedGraph.java    |   18 +
 .../edu/uci/ics/jung/graph/event/GraphEvent.java   |  117 ++
 .../ics/jung/graph/event/GraphEventListener.java   |   18 +
 .../java/edu/uci/ics/jung/graph/event/package.html |   19 +
 .../main/java/edu/uci/ics/jung/graph/package.html  |   53 +
 .../java/edu/uci/ics/jung/graph/util/Context.java  |   50 +
 .../util/DefaultParallelEdgeIndexFunction.java     |  148 +++
 .../uci/ics/jung/graph/util/EdgeIndexFunction.java |   54 +
 .../java/edu/uci/ics/jung/graph/util/EdgeType.java |   22 +
 .../java/edu/uci/ics/jung/graph/util/Graphs.java   |  989 ++++++++++++++++
 .../jung/graph/util/IncidentEdgeIndexFunction.java |  118 ++
 .../java/edu/uci/ics/jung/graph/util/Pair.java     |  249 ++++
 .../edu/uci/ics/jung/graph/util/TreeUtils.java     |  120 ++
 .../java/edu/uci/ics/jung/graph/util/package.html  |   28 +
 jung-api/src/site/site.xml                         |   14 +
 .../AbstractDirectedSparseMultigraphTest.java      |  131 +++
 .../uci/ics/jung/graph/AbstractHypergraphTest.java |  172 +++
 .../graph/AbstractOrderedSparseMultigraphTest.java |  162 +++
 .../graph/AbstractSortedSparseMultigraphTest.java  |  164 +++
 .../jung/graph/AbstractSparseMultigraphTest.java   |  160 +++
 .../uci/ics/jung/graph/AbstractSparseTreeTest.java |  104 ++
 .../uci/ics/jung/graph/AbstractTreeUtilsTest.java  |   43 +
 .../AbstractUndirectedSparseMultigraphTest.java    |  124 ++
 .../java/edu/uci/ics/jung/graph/util/PairTest.java |  118 ++
 jung-graph-impl-2.0.1-sources.jar                  |  Bin 37106 -> 0 bytes
 jung-graph-impl/assembly.xml                       |   19 +
 jung-graph-impl/pom.xml                            |   32 +
 .../java/edu/uci/ics/jung/graph/AbstractGraph.java |  244 ++++
 .../edu/uci/ics/jung/graph/AbstractTypedGraph.java |   97 ++
 .../edu/uci/ics/jung/graph/DelegateForest.java     |  339 ++++++
 .../java/edu/uci/ics/jung/graph/DelegateTree.java  |  361 ++++++
 .../graph/DirectedOrderedSparseMultigraph.java     |  113 ++
 .../uci/ics/jung/graph/DirectedSparseGraph.java    |  289 +++++
 .../ics/jung/graph/DirectedSparseMultigraph.java   |  262 +++++
 .../edu/uci/ics/jung/graph/OrderedKAryTree.java    |  807 +++++++++++++
 .../ics/jung/graph/OrderedSparseMultigraph.java    |  129 +++
 .../java/edu/uci/ics/jung/graph/SetHypergraph.java |  344 ++++++
 .../uci/ics/jung/graph/SortedSparseMultigraph.java |  128 +++
 .../java/edu/uci/ics/jung/graph/SparseGraph.java   |  373 ++++++
 .../edu/uci/ics/jung/graph/SparseMultigraph.java   |  320 ++++++
 .../graph/UndirectedOrderedSparseMultigraph.java   |   88 ++
 .../uci/ics/jung/graph/UndirectedSparseGraph.java  |  243 ++++
 .../ics/jung/graph/UndirectedSparseMultigraph.java |  249 ++++
 .../edu/uci/ics/jung/graph/util/TestGraphs.java    |  248 ++++
 jung-graph-impl/src/site/site.xml                  |   14 +
 .../jung/graph/DirectedSparseMultigraphTest.java   |   20 +
 .../edu/uci/ics/jung/graph/HypergraphTest.java     |   55 +
 .../jung/graph/OrderedSparseMultigraphTest.java    |   49 +
 .../ics/jung/graph/SortedSparseMultigraphTest.java |   57 +
 .../graph/SparseGraphAddRemoveAddEdgeTest.java     |   25 +
 .../uci/ics/jung/graph/SparseMultigraphTest.java   |   49 +
 .../edu/uci/ics/jung/graph/SparseTreeTest.java     |   21 +
 .../java/edu/uci/ics/jung/graph/TreeUtilsTest.java |   23 +
 .../jung/graph/UndirectedSparseMultigraphTest.java |   18 +
 jung-io-2.0.1-sources.jar                          |  Bin 56342 -> 0 bytes
 jung-io/pom.xml                                    |   36 +
 .../main/java/edu/uci/ics/jung/io/GraphFile.java   |   40 +
 .../java/edu/uci/ics/jung/io/GraphIOException.java |   55 +
 .../java/edu/uci/ics/jung/io/GraphMLMetadata.java  |   53 +
 .../java/edu/uci/ics/jung/io/GraphMLReader.java    |  831 ++++++++++++++
 .../java/edu/uci/ics/jung/io/GraphMLWriter.java    |  440 +++++++
 .../main/java/edu/uci/ics/jung/io/GraphReader.java |   45 +
 .../java/edu/uci/ics/jung/io/PajekNetReader.java   |  532 +++++++++
 .../java/edu/uci/ics/jung/io/PajekNetWriter.java   |  220 ++++
 .../uci/ics/jung/io/graphml/AbstractMetadata.java  |   40 +
 .../edu/uci/ics/jung/io/graphml/DataMetadata.java  |   41 +
 .../edu/uci/ics/jung/io/graphml/EdgeMetadata.java  |  114 ++
 .../uci/ics/jung/io/graphml/EndpointMetadata.java  |   68 ++
 .../ics/jung/io/graphml/ExceptionConverter.java    |   54 +
 .../uci/ics/jung/io/graphml/GraphMLConstants.java  |   48 +
 .../uci/ics/jung/io/graphml/GraphMLDocument.java   |   36 +
 .../uci/ics/jung/io/graphml/GraphMLReader2.java    |  368 ++++++
 .../edu/uci/ics/jung/io/graphml/GraphMetadata.java |  164 +++
 .../uci/ics/jung/io/graphml/HyperEdgeMetadata.java |   66 ++
 .../main/java/edu/uci/ics/jung/io/graphml/Key.java |   95 ++
 .../java/edu/uci/ics/jung/io/graphml/KeyMap.java   |  123 ++
 .../java/edu/uci/ics/jung/io/graphml/Metadata.java |   43 +
 .../edu/uci/ics/jung/io/graphml/NodeMetadata.java  |   66 ++
 .../edu/uci/ics/jung/io/graphml/PortMetadata.java  |   45 +
 .../io/graphml/parser/AbstractElementParser.java   |   60 +
 .../jung/io/graphml/parser/DataElementParser.java  |   93 ++
 .../jung/io/graphml/parser/EdgeElementParser.java  |  111 ++
 .../ics/jung/io/graphml/parser/ElementParser.java  |   28 +
 .../io/graphml/parser/ElementParserRegistry.java   |   71 ++
 .../io/graphml/parser/EndpointElementParser.java   |  120 ++
 .../jung/io/graphml/parser/GraphElementParser.java |  253 +++++
 .../jung/io/graphml/parser/GraphMLEventFilter.java |   41 +
 .../io/graphml/parser/HyperEdgeElementParser.java  |   98 ++
 .../jung/io/graphml/parser/KeyElementParser.java   |  133 +++
 .../jung/io/graphml/parser/NodeElementParser.java  |  103 ++
 .../ics/jung/io/graphml/parser/ParserContext.java  |   77 ++
 .../jung/io/graphml/parser/PortElementParser.java  |  102 ++
 .../io/graphml/parser/StringElementParser.java     |   66 ++
 .../io/graphml/parser/UnknownElementParser.java    |   83 ++
 .../src/main/java/edu/uci/ics/jung/io/package.html |   24 +
 jung-io/src/site/site.xml                          |   14 +
 .../java/edu/uci/ics/jung/io/PajekNetIOTest.java   |  433 +++++++
 .../edu/uci/ics/jung/io/TestGraphMLReader.java     |  280 +++++
 .../edu/uci/ics/jung/io/TestGraphMLWriter.java     |  169 +++
 .../edu/uci/ics/jung/io/graphml/DummyEdge.java     |   29 +
 .../ics/jung/io/graphml/DummyGraphObjectBase.java  |   53 +
 .../edu/uci/ics/jung/io/graphml/DummyVertex.java   |   21 +
 .../ics/jung/io/graphml/TestGraphMLReader2.java    |  447 ++++++++
 .../jung/io/graphml/parser/AbstractParserTest.java |   76 ++
 .../io/graphml/parser/TestEdgeElementParser.java   |  168 +++
 .../graphml/parser/TestEndpointElementParser.java  |  142 +++
 .../io/graphml/parser/TestGraphElementParser.java  |  207 ++++
 .../graphml/parser/TestHyperEdgeElementParser.java |  114 ++
 .../io/graphml/parser/TestKeyElementParser.java    |  166 +++
 .../io/graphml/parser/TestNodeElementParser.java   |  131 +++
 .../io/graphml/parser/TestPortElementParser.java   |   85 ++
 jung-io/src/test/resources/attributes.graphml      |   41 +
 .../edu/uci/ics/jung/io/graphml/attributes.graphml |   41 +
 .../edu/uci/ics/jung/io/graphml/hyper.graphml      |   29 +
 .../edu/uci/ics/jung/io/graphml/multigraph.graphml |   66 ++
 jung-io/src/test/resources/hyper.graphml           |   29 +
 jung-samples-2.0.1-sources.jar                     |  Bin 551968 -> 0 bytes
 jung-samples/pom.xml                               |   90 ++
 jung-samples/simple.graphml                        |   30 +
 jung-samples/src/main/assembly/assembly.xml        |   14 +
 .../java/edu/uci/ics/jung/samples/AddNodeDemo.java |  198 ++++
 .../uci/ics/jung/samples/AnimatingAddNodeDemo.java |  258 +++++
 .../edu/uci/ics/jung/samples/AnnotationsDemo.java  |  197 ++++
 .../uci/ics/jung/samples/BalloonLayoutDemo.java    |  337 ++++++
 .../edu/uci/ics/jung/samples/ClusteringDemo.java   |  333 ++++++
 .../samples/DemoLensVertexImageShaperDemo.java     |  419 +++++++
 .../uci/ics/jung/samples/DrawnIconVertexDemo.java  |  197 ++++
 .../edu/uci/ics/jung/samples/EdgeLabelDemo.java    |  349 ++++++
 .../edu/uci/ics/jung/samples/GraphEditorDemo.java  |  300 +++++
 .../uci/ics/jung/samples/GraphFromGraphMLDemo.java |  176 +++
 .../ics/jung/samples/GraphZoomScrollPaneDemo.java  |  261 +++++
 .../uci/ics/jung/samples/ImageEdgeLabelDemo.java   |  171 +++
 .../samples/InternalFrameSatelliteViewDemo.java    |  205 ++++
 .../uci/ics/jung/samples/L2RTreeLayoutDemo.java    |  297 +++++
 .../java/edu/uci/ics/jung/samples/LensDemo.java    |  449 ++++++++
 .../jung/samples/LensVertexImageShaperDemo.java    |  431 +++++++
 .../ics/jung/samples/MinimumSpanningTreeDemo.java  |  266 +++++
 .../edu/uci/ics/jung/samples/MultiViewDemo.java    |  335 ++++++
 .../uci/ics/jung/samples/PersistentLayoutDemo.java |  139 +++
 .../ics/jung/samples/PluggableRendererDemo.java    | 1198 ++++++++++++++++++++
 .../uci/ics/jung/samples/RadialTreeLensDemo.java   |  304 +++++
 .../uci/ics/jung/samples/SatelliteViewDemo.java    |  298 +++++
 .../edu/uci/ics/jung/samples/ShortestPathDemo.java |  309 +++++
 .../java/edu/uci/ics/jung/samples/ShowLayouts.java |  281 +++++
 .../edu/uci/ics/jung/samples/SimpleGraphDraw.java  |   58 +
 .../edu/uci/ics/jung/samples/SubLayoutDemo.java    |  393 +++++++
 .../edu/uci/ics/jung/samples/TreeCollapseDemo.java |  384 +++++++
 .../edu/uci/ics/jung/samples/TreeLayoutDemo.java   |  283 +++++
 .../edu/uci/ics/jung/samples/TwoModelDemo.java     |  185 +++
 .../edu/uci/ics/jung/samples/UnicodeLabelDemo.java |  243 ++++
 .../uci/ics/jung/samples/VertexCollapseDemo.java   |  346 ++++++
 .../samples/VertexCollapseDemoWithLayouts.java     |  446 ++++++++
 .../ics/jung/samples/VertexImageShaperDemo.java    |  553 +++++++++
 .../ics/jung/samples/VertexLabelAsShapeDemo.java   |  161 +++
 .../ics/jung/samples/VertexLabelPositionDemo.java  |  174 +++
 .../jung/samples/VisualizationImageServerDemo.java |  135 +++
 .../uci/ics/jung/samples/WorldMapGraphDemo.java    |  306 +++++
 .../java/edu/uci/ics/jung/samples/package.html     |   36 +
 .../src/main/resources/datasets/simple.net         |   33 +
 jung-samples/src/main/resources/datasets/smyth.net |  842 ++++++++++++++
 .../src/main/resources/datasets/weighted.net       |    8 +
 .../src/main/resources/datasets/zachary.net        |  114 ++
 .../src/main/resources/images/Sandstone.jpg        |  Bin 0 -> 51902 bytes
 jung-samples/src/main/resources/images/china.gif   |  Bin 0 -> 854 bytes
 jung-samples/src/main/resources/images/france.gif  |  Bin 0 -> 875 bytes
 jung-samples/src/main/resources/images/germany.gif |  Bin 0 -> 77 bytes
 jung-samples/src/main/resources/images/japan.gif   |  Bin 0 -> 863 bytes
 .../src/main/resources/images/lightning-s.gif      |  Bin 0 -> 1055 bytes
 .../main/resources/images/political_world_map.jpg  |  Bin 0 -> 442288 bytes
 jung-samples/src/main/resources/images/russia.gif  |  Bin 0 -> 856 bytes
 jung-samples/src/main/resources/images/spain.gif   |  Bin 0 -> 865 bytes
 .../src/main/resources/images/topicapple.gif       |  Bin 0 -> 1476 bytes
 .../main/resources/images/topicgamespcgames.gif    |  Bin 0 -> 2169 bytes
 .../src/main/resources/images/topicgraphics3.gif   |  Bin 0 -> 1667 bytes
 .../src/main/resources/images/topichumor.gif       |  Bin 0 -> 1298 bytes
 .../main/resources/images/topicinputdevices.gif    |  Bin 0 -> 1404 bytes
 .../src/main/resources/images/topiclinux.gif       |  Bin 0 -> 1988 bytes
 .../src/main/resources/images/topicmusic.gif       |  Bin 0 -> 1163 bytes
 jung-samples/src/main/resources/images/topicos.gif |  Bin 0 -> 1602 bytes
 .../src/main/resources/images/topicprivacy.gif     |  Bin 0 -> 1405 bytes
 .../src/main/resources/images/topicsample.gif      |  Bin 0 -> 833 bytes
 .../src/main/resources/images/topicsample2.gif     |  Bin 0 -> 827 bytes
 .../src/main/resources/images/topicwireless.gif    |  Bin 0 -> 1045 bytes
 jung-samples/src/main/resources/images/topicx.gif  |  Bin 0 -> 1256 bytes
 .../src/main/resources/images/united-states.gif    |  Bin 0 -> 877 bytes
 jung-samples/src/site/site.xml                     |   14 +
 jung-visualization-2.0.1-sources.jar               |  Bin 219700 -> 0 bytes
 jung-visualization/assembly.xml                    |   14 +
 jung-visualization/pom.xml                         |   36 +
 .../ics/jung/visualization/BasicTransformer.java   |  173 +++
 .../visualization/BasicVisualizationServer.java    |  508 +++++++++
 .../visualization/DefaultVisualizationModel.java   |  188 +++
 .../jung/visualization/FourPassImageShaper.java    |  251 ++++
 .../jung/visualization/GraphZoomScrollPane.java    |  328 ++++++
 .../java/edu/uci/ics/jung/visualization/Layer.java |    5 +
 .../uci/ics/jung/visualization/LayeredIcon.java    |   48 +
 .../jung/visualization/MultiLayerTransformer.java  |   28 +
 .../jung/visualization/PivotingImageShaper.java    |  208 ++++
 .../jung/visualization/PluggableRenderContext.java |  447 ++++++++
 .../uci/ics/jung/visualization/RenderContext.java  |  210 ++++
 .../visualization/VisualizationImageServer.java    |   71 ++
 .../ics/jung/visualization/VisualizationModel.java |   72 ++
 .../jung/visualization/VisualizationServer.java    |  198 ++++
 .../jung/visualization/VisualizationViewer.java    |  191 ++++
 .../annotations/AnnotatingGraphMousePlugin.java    |  304 +++++
 .../annotations/AnnotatingModalGraphMouse.java     |  264 +++++
 .../jung/visualization/annotations/Annotation.java |   99 ++
 .../annotations/AnnotationControls.java            |  130 +++
 .../annotations/AnnotationManager.java             |  152 +++
 .../annotations/AnnotationPaintable.java           |  129 +++
 .../annotations/AnnotationRenderer.java            |  192 ++++
 .../jung/visualization/annotations/package.html    |   19 +
 .../control/AbsoluteCrossoverScalingControl.java   |   55 +
 .../control/AbstractGraphMousePlugin.java          |   84 ++
 .../control/AbstractModalGraphMouse.java           |  305 +++++
 .../control/AbstractPopupGraphMousePlugin.java     |   51 +
 .../control/AnimatedPickingGraphMousePlugin.java   |  155 +++
 .../control/CrossoverScalingControl.java           |   81 ++
 .../control/CubicCurveEdgeEffects.java             |  140 +++
 .../control/DefaultModalGraphMouse.java            |  109 ++
 .../jung/visualization/control/EdgeEffects.java    |   25 +
 .../jung/visualization/control/EdgeSupport.java    |   25 +
 .../control/EditingGraphMousePlugin.java           |  178 +++
 .../control/EditingModalGraphMouse.java            |  312 +++++
 .../control/EditingPopupGraphMousePlugin.java      |  115 ++
 .../visualization/control/GraphMouseAdapter.java   |   42 +
 .../visualization/control/GraphMouseListener.java  |   29 +
 .../visualization/control/GraphMousePlugin.java    |   38 +
 .../control/LabelEditingGraphMousePlugin.java      |  156 +++
 .../control/LayoutScalingControl.java              |   42 +
 .../control/LensMagnificationGraphMousePlugin.java |  116 ++
 .../control/LensTranslatingGraphMousePlugin.java   |  184 +++
 .../visualization/control/ModalGraphMouse.java     |   36 +
 .../visualization/control/ModalLensGraphMouse.java |   95 ++
 .../control/ModalSatelliteGraphMouse.java          |   43 +
 .../control/MouseListenerTranslator.java           |   90 ++
 .../control/PickingGraphMousePlugin.java           |  376 ++++++
 .../visualization/control/PluggableGraphMouse.java |  153 +++
 .../control/RotatingGraphMousePlugin.java          |  197 ++++
 .../SatelliteAnimatedPickingGraphMousePlugin.java  |   87 ++
 .../control/SatelliteRotatingGraphMousePlugin.java |   73 ++
 .../control/SatelliteScalingGraphMousePlugin.java  |   63 +
 .../control/SatelliteShearingGraphMousePlugin.java |   82 ++
 .../SatelliteTranslatingGraphMousePlugin.java      |   75 ++
 .../control/SatelliteVisualizationViewer.java      |  129 +++
 .../jung/visualization/control/ScalingControl.java |   25 +
 .../control/ScalingGraphMousePlugin.java           |  131 +++
 .../control/ShearingGraphMousePlugin.java          |  166 +++
 .../visualization/control/SimpleEdgeSupport.java   |   86 ++
 .../visualization/control/SimpleVertexSupport.java |   56 +
 .../control/TranslatingGraphMousePlugin.java       |  125 ++
 .../jung/visualization/control/VertexSupport.java  |   22 +
 .../visualization/control/ViewScalingControl.java  |   40 +
 .../control/ViewTranslatingGraphMousePlugin.java   |  117 ++
 .../ics/jung/visualization/control/package.html    |   20 +
 .../decorators/AbstractVertexShapeTransformer.java |   57 +
 .../ConstantDirectionalEdgeValueTransformer.java   |   68 ++
 .../DirectionalEdgeArrowTransformer.java           |   50 +
 .../jung/visualization/decorators/EdgeShape.java   |  321 ++++++
 .../decorators/EllipseVertexShapeTransformer.java  |   37 +
 .../decorators/GradientEdgePaintTransformer.java   |  107 ++
 .../InterpolatingVertexSizeTransformer.java        |   72 ++
 .../decorators/NumberFormattingTransformer.java    |   42 +
 .../decorators/ParallelEdgeShapeTransformer.java   |   40 +
 .../decorators/PickableEdgePaintTransformer.java   |   59 +
 .../decorators/PickableVertexIconTransformer.java  |   55 +
 .../decorators/PickableVertexPaintTransformer.java |   55 +
 .../decorators/SettableVertexShapeTransformer.java |   29 +
 .../visualization/decorators/ToStringLabeller.java |   35 +
 .../decorators/VertexIconShapeTransformer.java     |  116 ++
 .../ics/jung/visualization/decorators/package.html |   20 +
 .../layout/BoundingRectangleCollector.java         |   86 ++
 .../layout/BoundingRectanglePaintable.java         |   56 +
 .../jung/visualization/layout/CachingLayout.java   |   69 ++
 .../visualization/layout/LayoutChangeListener.java |    7 +
 .../ics/jung/visualization/layout/LayoutEvent.java |   26 +
 .../visualization/layout/LayoutEventSupport.java   |    9 +
 .../visualization/layout/LayoutTransition.java     |   57 +
 .../layout/ObservableCachingLayout.java            |  140 +++
 .../visualization/layout/PersistentLayout.java     |   51 +
 .../visualization/layout/PersistentLayoutImpl.java |  173 +++
 .../uci/ics/jung/visualization/layout/package.html |   20 +
 .../edu/uci/ics/jung/visualization/package.html    |   19 +
 .../visualization/picking/AbstractPickedState.java |   44 +
 .../picking/ClosestShapePickSupport.java           |  141 +++
 .../picking/LayoutLensShapePickSupport.java        |  202 ++++
 .../visualization/picking/MultiPickedState.java    |   82 ++
 .../ics/jung/visualization/picking/PickedInfo.java |   23 +
 .../jung/visualization/picking/PickedState.java    |   48 +
 .../visualization/picking/RadiusPickSupport.java   |   98 ++
 .../visualization/picking/ShapePickSupport.java    |  472 ++++++++
 .../picking/ViewLensShapePickSupport.java          |  233 ++++
 .../ics/jung/visualization/picking/package.html    |   19 +
 .../renderers/BasicEdgeArrowRenderingSupport.java  |  203 ++++
 .../renderers/BasicEdgeLabelRenderer.java          |  113 ++
 .../visualization/renderers/BasicEdgeRenderer.java |  293 +++++
 .../visualization/renderers/BasicRenderer.java     |  137 +++
 .../renderers/BasicVertexLabelRenderer.java        |  202 ++++
 .../renderers/BasicVertexRenderer.java             |  136 +++
 .../renderers/CachingEdgeRenderer.java             |  180 +++
 .../visualization/renderers/CachingRenderer.java   |   15 +
 .../renderers/CachingVertexRenderer.java           |   77 ++
 .../renderers/CenterEdgeArrowRenderingSupport.java |  158 +++
 .../jung/visualization/renderers/Checkmark.java    |   63 +
 .../renderers/DefaultEdgeLabelRenderer.java        |  228 ++++
 .../renderers/DefaultVertexLabelRenderer.java      |  204 ++++
 .../renderers/EdgeArrowRenderingSupport.java       |   63 +
 .../visualization/renderers/EdgeLabelRenderer.java |   45 +
 .../renderers/GradientVertexRenderer.java          |  141 +++
 .../ics/jung/visualization/renderers/Renderer.java |   92 ++
 .../renderers/ReshapingEdgeRenderer.java           |  411 +++++++
 .../renderers/VertexLabelAsShapeRenderer.java      |  121 ++
 .../renderers/VertexLabelRenderer.java             |   41 +
 .../ics/jung/visualization/renderers/package.html  |   19 +
 .../jung/visualization/spatial/AggregateGraph.java |  271 +++++
 .../visualization/spatial/FastRenderingGraph.java  |  315 +++++
 .../visualization/spatial/FastRenderingLayout.java |   74 ++
 .../visualization/subLayout/GraphCollapser.java    |  189 +++
 .../visualization/subLayout/TreeCollapser.java     |   53 +
 .../ics/jung/visualization/subLayout/package.html  |   19 +
 .../transform/AbstractLensSupport.java             |  185 +++
 .../visualization/transform/AffineTransformer.java |  273 +++++
 .../transform/BidirectionalTransformer.java        |   36 +
 .../transform/HyperbolicTransformer.java           |  120 ++
 .../visualization/transform/LayoutLensSupport.java |   88 ++
 .../jung/visualization/transform/LensSupport.java  |   29 +
 .../visualization/transform/LensTransformer.java   |  175 +++
 .../transform/MagnifyTransformer.java              |  146 +++
 .../transform/MutableAffineTransformer.java        |  218 ++++
 .../transform/MutableTransformer.java              |   67 ++
 .../transform/MutableTransformerDecorator.java     |  151 +++
 .../ics/jung/visualization/transform/package.html  |   19 +
 .../transform/shape/Graphics2DWrapper.java         |  674 +++++++++++
 .../transform/shape/GraphicsDecorator.java         |   50 +
 .../shape/HyperbolicShapeTransformer.java          |  226 ++++
 .../visualization/transform/shape/Intersector.java |  116 ++
 .../transform/shape/MagnifyIconGraphics.java       |   85 ++
 .../transform/shape/MagnifyImageLensSupport.java   |  106 ++
 .../transform/shape/MagnifyShapeTransformer.java   |  271 +++++
 .../transform/shape/ShapeFlatnessTransformer.java  |   35 +
 .../transform/shape/ShapeTransformer.java          |   34 +
 .../shape/TransformingFlatnessGraphics.java        |   62 +
 .../transform/shape/TransformingGraphics.java      |  150 +++
 .../transform/shape/ViewLensSupport.java           |   93 ++
 .../visualization/transform/shape/package.html     |   19 +
 .../uci/ics/jung/visualization/util/Animator.java  |   82 ++
 .../ics/jung/visualization/util/ArrowFactory.java  |   65 ++
 .../uci/ics/jung/visualization/util/Caching.java   |   32 +
 .../visualization/util/ChangeEventSupport.java     |   42 +
 .../util/DefaultChangeEventSupport.java            |   79 ++
 .../visualization/util/GeneralPathAsString.java    |   51 +
 .../jung/visualization/util/ImageShapeUtils.java   |  105 ++
 .../ics/jung/visualization/util/LabelWrapper.java  |   87 ++
 .../util/PredicatedParallelEdgeIndexFunction.java  |  152 +++
 .../visualization/util/VertexShapeFactory.java     |  224 ++++
 .../uci/ics/jung/visualization/util/package.html   |   19 +
 jung-visualization/src/site/site.xml               |   14 +
 .../BasicVisualizationServerTest.java              |   23 +
 .../ics/jung/visualization/TestImageShaper.java    |   55 +
 .../control/TestCrossoverScalingControl.java       |   76 ++
 pom.xml                                            |  264 +++++
 tools/deploy_snapshots.sh                          |   13 +
 tools/mvn-deploy.sh                                |   21 +
 tools/settings.xml                                 |    9 +
 529 files changed, 70163 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7c28d6d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,25 @@
+#General
+*.orig
+~*
+*~
+*.bak
+
+# Maven
+target/
+*.ser
+*.ec
+
+# IntelliJ Idea
+.idea/
+out/
+*.ipr
+*.iws
+*.iml
+
+# Eclipse
+.classpath
+.project
+.settings/
+.metadata/
+.factoryPath
+bin/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..6473f06
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,35 @@
+# Per https://docs.travis-ci.com/user/languages/java
+
+sudo: false
+
+language: java
+
+jdk:
+  - oraclejdk8
+  - oraclejdk7
+  - openjdk7
+
+install:
+  - mvn -v
+  - mvn -B install -U -DskipTests=true
+
+script:
+  - mvn -B verify -U -Dmaven.javadoc.skip=true
+
+after_success:
+  - tools/deploy_snapshots.sh
+
+cache:
+  directories:
+  - ${HOME}/.m2
+
+branches:
+  only:
+    - master
+    - /^release.*$/
+
+env:
+  global:
+    -  secure: "ZDWaFgXd/IyJUt4vs9pZnljLyhjbAzvTpkWMg+K0aaUhn+NV3cLHuKvghJXSXTF0/dbKRO4wVWat1xNZm6Ec1zWVeRmmJhoqjh65DozAslQdZAnbEGxEMdXPNqxFs0bEijEQ2GUbdh18tjqDonZn6gEXExr/OFiANJD9lkUWUZkX3TieXrMV8l4H5ywyokYzDO2iKRHKLZeci9oBWdmd6/ER9Luumn67wesZbGKZ9OSODtXGjqTdrh+WpvH9NmrqKCZLCFG1TLAD0cUkqKLba5aXTRD0WIRyX8iZwQzYF8v+j2812x3PHKKN85OGbBtVU+FrUqbvmYtTpGMjn4DKO1XMUvAXSXdpehY71Vw7eU5XXvm4OihOzDLW2hV//cE0yjuqjQDLC9jA2IACkTq7z8oQJ8JLzOxnWQAWJdS5893ZXOIBCn/yifVGIwDUA8lU2RiippCJkplpQoYMv//NN8t8Aazp [...]
+    -  secure: "lgiLAdXkJZzTwWmZoX5b+/ZZJUYvI1qB/tAlnZuCxiupxv3Lx9JcYH7eI5zKEjCc8ahwxF0A7ZAa+KPEeEQ9RlvrtSY1jBVHCZWIo1rvgg9Jeynhs9yYBXhcgxYl4jGlp7AZz7ZviHNLXtKbFlf/DJnCIruoIetW/Na0Y+k124OAOuHZrObOM5bhldVzYPMl8lZP171k67M3e9y5Lxv4o41DKOGNzBo3oGtfprW/cO3Nna6UfthbTuzrsqGXYPLU5PN3xhKyEgk8byC2hkzXZzje1IgKn6qLvkhKPNWmYRIf+DFO4f22bMLtzfXG2gfAb9RHMDl1TvemOBA/VIKhGAwnzhJqEkcVY482n39E9bq+/Wqhliwv+XVcBnh8oNMbr8OUEP9OO1ys4isNSMysaG3LPGdYt9d82CFpWDO1gFZ3qdxze/y7Fd/yC+mu62KFn6Jxg9w94kGIzWqrv9DUDgLMougC [...]
+
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..dbdab05
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,19 @@
+JUNG was originally created in 2003 by (in alphabetical order):
+
+Danyel Fisher
+Joshua O'Madadhain
+Scott White
+
+Later contributors have included:
+
+Tom Nelson
+Yan-Biao Boey
+W. Giordano
+Masanori Harada
+Nathan Mittler
+Jasper Voskuilen
+John Yesberg
+
+Sponsoring organizations:
+Regents of the University of California
+Google, Inc.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..e0f80e3
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,35 @@
+Contributing
+============
+
+If you would like to contribute code to the JUNG Project you can do so through
+GitHub by forking the repository and sending a pull request.
+
+When submitting code, please make every effort to follow existing conventions
+and style in order to keep the code as readable as possible.  
+
+Where appropriate, please provide unit tests. Unit tests should be JUnit based
+and should be added to `/jung/<subproject>/src/test/java`.
+
+Please make sure your code compiles by running `mvn clean test` which will
+build and run the tests. All pull requests will be validated by Travis-ci
+in any case and typically must pass before being merged. 
+
+If you are adding or modifying files you should add the following copyright
+statement in a language-appropriate comment at the top of the file:
+
+```
+/*
+ * Copyright (c) <year>, the JUNG Project and the Regents of the University 
+ * of California.  All rights reserved.
+ *
+ * This software is open-source under the BSD license; see
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+```
+
+All files will be released to users of JUNG under a BSD license
+
+Before your code can be accepted into the project you must sign the
+[Individual Contributor License Agreement (CLA)][CLA].
+
+[CLA]: https://cla-assistant.io/jrtom/jung
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8e9eaf6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,41 @@
+The license below is the BSD open-source license. See 
+	http://www.opensource.org/licenses/BSD-3-Clause
+with:
+	<OWNER> = Regents of the University of California and the JUNG Project Authors
+	<ORGANIZATION> = University of California
+	<YEAR> = 2003
+
+It allows redistribution of JUNG freely, albeit with acknowledgement of JUNG's being
+a component in the redistributed software. 
+However, we would greatly appreciate it if you can let us know what you are doing with JUNG.
+
+--
+THE JUNG LICENSE
+
+Copyright (c) 2003-2015,  Regents of the University of California and the JUNG Project Authors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of JUNG, nor that of the University of California, nor the names of its
+  contributors, may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e9fe9bb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,52 @@
+## JUNG: The Java Universal Network/Graph Framework 
+
+[![Build Status](https://travis-ci.org/jrtom/jung.svg?branch=master)](https://travis-ci.org/jrtom/jung)
+[![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.sf.jung/jung-algorithms/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.sf.jung/jung-algorithms)
+
+JUNG is a software library that provides a common and extendible language for the modeling, analysis, and visualization of
+data that can be represented as a graph or network.  Its basis in Java allows JUNG-based applications to make use of the
+extensive built-in capabilities of the Java API, as well as those of other existing third-party Java libraries.
+
+[**JUNG Website**](http://jrtom.github.io/jung/)
+
+### Latest Release
+
+The most recent version of JUNG is [version 2.1](https://github.com/jrtom/jung/releases/tag/jung-2.1), released 18 March 2016.
+*   [Javadoc](http://jrtom.github.io/jung/javadoc/index.html)
+*   [Maven Search Repository](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22net.sf.jung%22%20AND%20v%3A%222.1%22%20AND%20(a%3A%22jung-api%22%20OR%20a%3A%22jung-graph-impl%22%20OR%20a%3A%22jung-visualization%22%20OR%20a%3A%22jung-algorithms%22%20OR%20a%3A%22jung-samples%22%20OR%20a%3A%22jung-io%22))
+    *   `jung-api`: [jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-api/2.1/jung-api-2.1.jar), [source jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-api/2.1/jung-api-2.1-sources.jar), [documentation jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-api/2.1/jung-api-2.1-javadoc.jar)
+    *   `jung-graph-impl`: [jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-graph-impl/2.1/jung-graph-impl-2.1.jar), [source jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-graph-impl/2.1/jung-graph-impl-2.1-sources.jar), [documentation jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-graph-impl/2.1/jung-graph-impl-2.1-javadoc.jar)
+    *   `jung-algorithms`: [jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-algorithms/2.1/jung-algorithms-2.1.jar), [source jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-algorithms/2.1/jung-algorithms-2.1-sources.jar), [documentation jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-algorithms/2.1/jung-algorithms-2.1-javadoc.jar)
+    *   `jung-io`: [jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-io/2.1/jung-io-2.1.jar), [source jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-io/2.1/jung-io-2.1-sources.jar), [documentation jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-io/2.1/jung-io-2.1-javadoc.jar)
+    *   `jung-visualization`: [jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-visualization/2.1/jung-visualization-2.1.jar), [source jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-visualization/2.1/jung-visualization-2.1-sources.jar), [documentation jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-visualization/2.1/jung-visualization-2.1-javadoc.jar)
+    *   `jung-samples`: [jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-samples/2.1/jung-samples-2.1.jar), [source jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-samples/2.1/jung-samples-2.1-sources.jar), [documentation jar](http://search.maven.org/remotecontent?filepath=net/sf/jung/jung-samples/2.1/jung-samples-2.1-javadoc.jar)
+
+To add a dependency on this release of JUNG using Maven, use the following for each JUNG subpackage that you need:
+
+```xml
+<dependency>
+  <groupId>net.sf.jung</groupId>
+  <artifactId>jung-[subpackage]</artifactId>
+  <version>2.1</version>
+</dependency>
+```
+
+### Snapshots
+
+Snapshots of JUNG built from the `master` branch are available through Maven using version `2.2-SNAPSHOT`.
+
+### Links
+
+* [GitHub project](https://github.com/jrtom/jung)
+* [Issue tracker: report a defect or make a feature request](https://github.com/jrtom/jung/issues/new)
+* [StackOverflow: Ask "how-to" and "why-didn't-it-work" questions](https://stackoverflow.com/questions/ask?tags=jung+java)
+
+### Contributions
+
+JUNG is currently administered primarily by @jrtom, one of the original co-creators of the JUNG project.
+
+Bug fixes (with tests) are appreciated and will generally be acted upon pretty quickly if the fix is a clear win.  
+
+If you'd like to add a feature, or suggest a way that things could be done better, more cleanly, or more efficiently, we really appreciate it, we encourage you to [open an issue](https://github.com/jrtom/jung/issues/new), and you're welcome to make a pull request to show off a proof of concept.
+
+However, at the moment we're largely focused on some big architectural changes that are going to touch essentially everything in JUNG.  Once those changes land, we'll have more time and energy available to consider other changes.
diff --git a/RELEASE.md b/RELEASE.md
new file mode 100644
index 0000000..632b609
--- /dev/null
+++ b/RELEASE.md
@@ -0,0 +1,229 @@
+# Releasing JUNG
+
+## Overview
+
+At a high level, the steps involved are as follows:
+
+  * Preconditions
+  * Create a release branch
+  * Update versions
+  * Tag the release
+  * Build and deploy the release to sonatype
+  * Verify the release on sonatype
+  * Release the bits on oss.sonatype.org to the public repo
+  * Push the tag to github
+
+
+Each step has some idiosyncracies, and follows below.
+
+> ***Note:*** *Any specific version numbers should be assumed to be examples and the real,
+> current version numbers should be substituted.*
+
+## Detail
+
+### Preconditions
+
+> ***Note:*** *These preconditions include important minutiae of Maven
+> deployments.  Make sure you read the [OSSRH Guide] and the [Sonatype GPG
+> blog post][GPG].*
+
+Releases involve releasing to Sonatype's managed repository which backs the
+maven central repository.  To push bits to sonatype requires:
+
+  1. an account on oss.sonatype.org
+  2. permission for that account to push to your groupId
+  3. a pgp certificate (via gnupg) with a published public key
+  4. a [${HOME}/.m2/settings.xml][settings.xml] file containing the credentials
+     for the account created in step #1.  **NOTE**: change the ID for the <server> tag
+     in the example to `sonatype-nexus-staging`.
+
+The administrative steps above are all documented in Sonatype's
+[OSSRH Guide]. The GPG instructions particular to this process can be found
+in this [Sonatype GPG blog entry][GPG].
+
+> ***Notes***:
+> *   *If you don't set up the `settings.xml` correctly on the machine you're using,
+>     you'll get an error that looks like this:*
+> ```shell
+> Failed to transfer file: https://oss.sonatype.org/service/local/staging/deploy/maven2/net/sf/jung/jung-parent/2.1/jung-parent-2.1.pom. 
+Return code is: 401, ReasonPhrase: Unauthorized.
+> ```
+> *   *As of this writing (March 2016) the default GPG installation on OS X will give you a
+>     binary called `gpg2` rather than `gpg`.  This will cause the deploy script to fail.  
+>     You should create a symbolic link called `gpg` (using `ln -s`) that points to `gpg2`.*
+
+### Create a release branch
+
+First check out the main project's master branch, and create a branch on which
+to do the release work (to avoid clobbering anything on the master branch):
+
+```shell
+git clone git at github.com:jrtom/jung.git jung_release
+cd jung_release
+git checkout -b release_2_1_branch
+mvn verify
+```
+
+This generates a new branch, and does a full build to ensure that what is
+currently at the tip of the branch is sound.
+
+### Update versions
+
+#### Increment SNAPSHOT dependency versions
+
+Do a quick check of the dependency versions to ensure that the project is
+not relying on -SNAPSHOT dependencies. Since the project manages versions
+in a `properties` section in the parent pom, the following is a useful tool:
+
+```shell
+mvn -N versions:display-property-updates
+```
+
+Version properties will be generated and look like this:
+
+```
+...
+[INFO] ------------------------------------------------------------------------
+[INFO] Building jung-parent 2.1-SNAPSHOT
+[INFO] ------------------------------------------------------------------------
+[INFO] 
+[INFO] --- versions-maven-plugin:2.2:display-property-updates (default-cli) @ jung-parent ---
+[INFO] artifact junit:junit: checking for updates from central
+[INFO] artifact com.google.guava:guava: checking for updates from central
+[INFO] 
+[INFO] The following version properties are referencing the newest available version:
+[INFO]   ${guava.version} ............................................... 19.0
+[INFO] The following version property updates are available:
+[INFO]   ${junit.version} ...................................... 3.8.1 -> 4.12
+...
+```
+
+For release, it's best to avoid updating older versions at the last minute, as
+this requires more testing and investigation than one typically does at release.
+But releases are gated on any -SNAPSHOT dependencies, so these should be
+incremented.
+
+> ***Note:*** *If there is enough dependency lag, the release should be abandoned
+> and dependencies should be incremented as a normal part of development.*
+
+#### Update the project's version.
+
+Update the versions of the project, like so (changing version numbers):
+
+```shell
+mvn versions:set versions:commit -DnewVersion=2.1
+git commit -a
+```
+
+This will set all versions of projects connected in <module> sections from
+the parent pom - in short, all the parts of the project will be set to be (and
+depend on) `2.1`.
+
+### Tag the release
+
+The release tags simply follow the format `jung-<version>` so simply do this:
+
+```shell
+git tag jung-2.1
+```
+
+### Build and deploy the release to sonatype
+
+A convenience script exists to properly run a standard `mvn deploy` run
+(which pushes built artifacts to the staging repository).  It also activates
+the release profile which ensures that the GnuPG plugin is run, signing the
+binaries per Sonatype's requirements, adds in the generation of -javadoc and
+-sources jars, etc.
+
+It's parameter is the label for your GnuPG key which can be seen by running
+`gpg --list-keys` which supplies output similar to the following:
+
+```
+pub   2048D/E4382034 2014-12-16
+uid                  Some User (Maven Deployments) <foo at bar.com>
+```
+
+> More detail about GPG and Sonatype repositories [in this blog post][GPG]
+
+Given the above example, you would then run:
+
+```shell
+tools/mvn-deploy.sh E4382034
+```
+
+... and the script will kick off the maven job, pausing when it first needs to
+sign binaries to ask for your GnuPG certificate passphrase (if any).  It then
+pushes the binaries and signatures up to sonatype's staging repository.
+
+> ***Note:*** *Having out-of-date versions of Maven plugins can cause unexpected
+> errors in the build/deploy process, including failure to find local binaries,
+> and apparent compilation errors.  In case of bizarre failures, update the 
+> plugins to the [latest versions](https://maven.apache.org/plugins/) and try again.*
+
+### Verify the release on sonatype
+
+Log in to `oss.sonatype.org` and select "Staging repositories".  In the
+main window, scroll to the botton where a staging repository named roughly
+after the groupId will appear.
+
+> ***Note:*** *while this can be inspected, Sonatype performs several checks
+> automatically when going through the release lifecycle, so generally it is
+> not necessary to further inspect this staging repo.*
+
+Select the repository.  You can check to ensure it is the correct repository by
+descending the tree in the lower info window.  If you are convinced it is the
+correct one, click on the `close` button (in the upper menu bar) and optionally
+enter a message (which will be included in any notifications people have set
+up on that repository).  Wait about 60 seconds or so and refresh.
+
+If successful, the `release` button will be visible.
+
+#### What if it goes wrong?
+
+If sonatype's analysis has rejected the release, you can check the information
+in the lower info window to see what went wrong.  Failed analyzes will show
+in red, and the problem should be remedied and step #3 (Tag the release) should
+be re-attempted with `tag -f jung-<version>` once the fixes have been
+committed.  Then subsequent steps repeated.
+
+### Release the bits on oss.sonatype.org to the public repo
+
+Assuming sonatype's validation was successful, press the `release` button,
+fill in the optional message, and the repository will be released and
+automatically dropped once its contents have been copied out to the master
+repository.
+
+At this point, the maven artifact(s) will be available for consumption by
+maven builds within a few minutes (though it will not be present on
+<http://search.maven.org> for about an hour).
+
+### Push the tag to github
+
+Since the release was committed to the maven repository, the exact project
+state used to generate that should be marked.  To push the above-mentioned
+tag to github, just do the standard git command:
+
+```shell
+git push --tags
+```
+
+This will create a new [release](https://github.com/jrtom/jung/releases) on GitHub.
+
+## Post-release
+
+Create a CL/commit that updates the versions from (for instance)
+`2.1-SNAPSHOT` to the next development version (typically `2.2-SNAPSHOT`).
+This commit should also contain any changes that were necessary to release
+the project which need to be persisted (any upgraded dependencies, etc.)
+
+> ***Note:*** *Generally do not merge this directly into github as that will disrupt
+> the standard MOE sync.  It can either be created as a github pull-request and
+> the `moe github_pull` command will turn it into a CL, or it can be created
+> in a normal internal CL. The change can then by synced-out in the MOE run.*
+
+Once the release is done, and the tag is pushed, the branch can be safely
+deleted.
+
+[GPG]: http://blog.sonatype.com/2010/01/how-to-generate-pgp-signatures-with-maven
+[OSSRH Guide]: http://central.sonatype.org/pages/ossrh-guide.html
+[settings.xml]: https://books.sonatype.com/nexus-book/reference/_adding_credentials_to_your_maven_settings.html
diff --git a/jung-algorithms-2.0.1-sources.jar b/jung-algorithms-2.0.1-sources.jar
deleted file mode 100644
index a2a18f6..0000000
Binary files a/jung-algorithms-2.0.1-sources.jar and /dev/null differ
diff --git a/jung-algorithms/assembly.xml b/jung-algorithms/assembly.xml
new file mode 100644
index 0000000..81fb092
--- /dev/null
+++ b/jung-algorithms/assembly.xml
@@ -0,0 +1,19 @@
+<assembly>
+  <id>dependencies</id>
+  <formats>
+    <format>tar.gz</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <fileSets>
+    <fileSet>
+      <outputDirectory>/</outputDirectory>
+    </fileSet>
+  </fileSets>
+  <dependencySets>
+    <dependencySet>
+      <outputDirectory>/</outputDirectory>
+      <unpack>false</unpack>
+      <scope>runtime</scope>
+    </dependencySet>
+  </dependencySets>
+</assembly>
diff --git a/jung-algorithms/pom.xml b/jung-algorithms/pom.xml
new file mode 100644
index 0000000..62871d3
--- /dev/null
+++ b/jung-algorithms/pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>net.sf.jung</groupId>
+    <artifactId>jung-parent</artifactId>
+    <version>2.1.1</version>
+  </parent>
+  <artifactId>jung-algorithms</artifactId>
+  <name>JUNG - Algorithms</name>
+  <description>Algorithms for the JUNG project</description>
+
+  <dependencies>
+    <dependency>
+      <groupId>net.sf.jung</groupId>
+      <artifactId>jung-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>net.sf.jung</groupId>
+      <artifactId>jung-graph-impl</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/blockmodel/StructurallyEquivalent.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/blockmodel/StructurallyEquivalent.java
new file mode 100644
index 0000000..cbf0692
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/blockmodel/StructurallyEquivalent.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2004, The JUNG Authors 
+ *
+ * All rights reserved.
+ * Created on Jan 28, 2004
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.blockmodel;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * Identifies sets of structurally equivalent vertices in a graph. Vertices <i>
+ * i</i> and <i>j</i> are structurally equivalent iff the set of <i>i</i>'s
+ * neighbors is identical to the set of <i>j</i>'s neighbors, with the
+ * exception of <i>i</i> and <i>j</i> themselves. This algorithm finds all
+ * sets of equivalent vertices in O(V^2) time.
+ * 
+ * <p>You can extend this class to have a different definition of equivalence (by
+ * overriding <code>isStructurallyEquivalent</code>), and may give it hints for
+ * accelerating the process by overriding <code>canPossiblyCompare</code>. 
+ * (For example, in a bipartite graph, <code>canPossiblyCompare</code> may 
+ * return <code>false</code> for vertices in
+ * different partitions. This function should be fast.)
+ * 
+ * @author Danyel Fisher
+ */
+public class StructurallyEquivalent<V,E> implements Function<Graph<V,E>, VertexPartition<V,E>> 
+{
+	public VertexPartition<V,E> apply(Graph<V,E> g) 
+	{
+	    Set<Pair<V>> vertex_pairs = getEquivalentPairs(g);
+	    
+	    Set<Set<V>> rv = new HashSet<Set<V>>();
+        Map<V, Set<V>> intermediate = new HashMap<V, Set<V>>();
+        for (Pair<V> p : vertex_pairs)
+        {
+            Set<V> res = intermediate.get(p.getFirst());
+            if (res == null)
+                res = intermediate.get(p.getSecond());
+            if (res == null)  // we haven't seen this one before
+                res = new HashSet<V>();
+            res.add(p.getFirst());
+            res.add(p.getSecond());
+            intermediate.put(p.getFirst(), res);
+            intermediate.put(p.getSecond(), res);
+        }
+        rv.addAll(intermediate.values());
+
+        // pick up the vertices which don't appear in intermediate; they are
+        // singletons (equivalence classes of size 1)
+        Collection<V> singletons = new ArrayList<V>(g.getVertices());
+        singletons.removeAll(intermediate.keySet());
+        for (V v : singletons)
+        {
+            Set<V> v_set = Collections.singleton(v);
+            intermediate.put(v, v_set);
+            rv.add(v_set);
+        }
+
+        return new VertexPartition<V, E>(g, intermediate, rv);
+	}
+
+	/**
+	 * For each vertex pair v, v1 in G, checks whether v and v1 are fully
+	 * equivalent: meaning that they connect to the exact same vertices. (Is
+	 * this regular equivalence, or whathaveyou?)
+	 * 
+	 * @param g the graph whose equivalent pairs are to be generated
+	 * @return a Set of Pairs of vertices, where all the vertices in the inner
+	 * Pairs are equivalent.
+	 */
+	protected Set<Pair<V>> getEquivalentPairs(Graph<V,?> g) {
+
+		Set<Pair<V>> rv = new HashSet<Pair<V>>();
+		Set<V> alreadyEquivalent = new HashSet<V>();
+
+		List<V> l = new ArrayList<V>(g.getVertices());
+
+		for (V v1 : l)
+		{
+			if (alreadyEquivalent.contains(v1))
+				continue;
+
+			for (Iterator<V> iterator = l.listIterator(l.indexOf(v1) + 1); iterator.hasNext();) {
+			    V v2 = iterator.next();
+
+				if (alreadyEquivalent.contains(v2))
+					continue;
+
+				if (!canBeEquivalent(v1, v2))
+					continue;
+
+				if (isStructurallyEquivalent(g, v1, v2)) {
+					Pair<V> p = new Pair<V>(v1, v2);
+					alreadyEquivalent.add(v2);
+					rv.add(p);
+				}
+			}
+		}
+		
+		return rv;
+	}
+
+	/**
+	 * @param g the graph in which the structural equivalence comparison is to take place
+	 * @param v1 the vertex to check for structural equivalence to v2
+	 * @param v2 the vertex to check for structural equivalence to v1
+	 * @return {@code true} if {@code v1}'s predecessors/successors are equal to
+	 *     {@code v2}'s predecessors/successors
+	 */
+	protected boolean isStructurallyEquivalent(Graph<V,?> g, V v1, V v2) {
+		
+		if( g.degree(v1) != g.degree(v2)) {
+			return false;
+		}
+
+		Set<V> n1 = new HashSet<V>(g.getPredecessors(v1));
+		n1.remove(v2);
+		n1.remove(v1);
+		Set<V> n2 = new HashSet<V>(g.getPredecessors(v2));
+		n2.remove(v1);
+		n2.remove(v2);
+
+		Set<V> o1 = new HashSet<V>(g.getSuccessors(v1));
+		Set<V> o2 = new HashSet<V>(g.getSuccessors(v2));
+		o1.remove(v1);
+		o1.remove(v2);
+		o2.remove(v1);
+		o2.remove(v2);
+
+		// this neglects self-loops and directed edges from 1 to other
+		boolean b = (n1.equals(n2) && o1.equals(o2));
+		if (!b)
+			return b;
+		
+		// if there's a directed edge v1->v2 then there's a directed edge v2->v1
+		b &= ( g.isSuccessor(v1, v2) == g.isSuccessor(v2, v1));
+		
+		// self-loop check
+		b &= ( g.isSuccessor(v1, v1) == g.isSuccessor(v2, v2));
+
+		return b;
+
+	}
+
+	/**
+	 * This is a space for optimizations. For example, for a bipartite graph,
+	 * vertices from different partitions cannot possibly be equivalent.
+	 * 
+	 * @param v1 the first vertex to compare
+	 * @param v2 the second vertex to compare
+	 * @return {@code true} if the vertices can be equivalent
+	 */
+	protected boolean canBeEquivalent(V v1, V v2) {
+		return true;
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/blockmodel/VertexPartition.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/blockmodel/VertexPartition.java
new file mode 100644
index 0000000..dea478b
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/blockmodel/VertexPartition.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Feb 3, 2004
+ */
+package edu.uci.ics.jung.algorithms.blockmodel;
+
+import java.util.*;
+
+import edu.uci.ics.jung.graph.Graph;
+
+
+/**
+ * Maintains information about a vertex partition of a graph.
+ * This can be built from a map from vertices to vertex sets 
+ * or from a collection of (disjoint) vertex sets,
+ * such as those created by various clustering methods.
+ */
+public class VertexPartition<V,E> 
+{
+	private Map<V,Set<V>> vertex_partition_map;
+	private Collection<Set<V>> vertex_sets;
+	private Graph<V,E> graph;
+	
+	/**
+	 * Creates an instance based on the specified graph and mapping from vertices
+	 * to vertex sets, and generates a set of partitions based on this mapping.
+	 * @param g the graph over which the vertex partition is defined
+	 * @param partition_map the mapping from vertices to vertex sets (partitions)
+	 */
+	public VertexPartition(Graph<V,E> g, Map<V, Set<V>> partition_map) 
+	{
+		this.vertex_partition_map = Collections.unmodifiableMap(partition_map);
+		this.graph = g;
+	}
+
+	/**
+     * Creates an instance based on the specified graph, vertex-set mapping, 
+     * and set of disjoint vertex sets.  The vertex-set mapping and vertex 
+     * partitions must be consistent; that is, the mapping must reflect the 
+     * division of vertices into partitions, and each vertex must appear in 
+     * exactly one partition.
+     * @param g the graph over which the vertex partition is defined
+     * @param partition_map the mapping from vertices to vertex sets (partitions)
+	 * @param vertex_sets the set of disjoint vertex sets 
+	 */
+    public VertexPartition(Graph<V,E> g, Map<V, Set<V>> partition_map, 
+    		Collection<Set<V>> vertex_sets) 
+    {
+        this.vertex_partition_map = Collections.unmodifiableMap(partition_map);
+        this.vertex_sets = vertex_sets;
+        this.graph = g;
+    }
+
+    /**
+     * Creates an instance based on the specified graph and set of disjoint vertex sets, 
+     * and generates a vertex-to-partition map based on these sets.
+     * @param g the graph over which the vertex partition is defined
+     * @param vertex_sets the set of disjoint vertex sets
+     */
+    public VertexPartition(Graph<V,E> g, Collection<Set<V>> vertex_sets)
+    {
+        this.vertex_sets = vertex_sets;
+        this.graph = g;
+    }
+	
+    /**
+     * Returns the graph on which the partition is defined.
+     * @return the graph on which the partition is defined
+     */
+	public Graph<V,E> getGraph() 
+	{
+		return graph;
+	}
+
+	/**
+	 * Returns a map from each vertex in the input graph to its partition.
+	 * This map is generated if it does not already exist.
+	 * @return a map from each vertex in the input graph to a vertex set
+	 */
+	public Map<V,Set<V>> getVertexToPartitionMap() 
+	{
+		if (vertex_partition_map == null)
+		{
+	        this.vertex_partition_map = new HashMap<V, Set<V>>();
+	        for (Set<V> set : this.vertex_sets)
+	            for (V v : set)
+	                this.vertex_partition_map.put(v, set);
+		}
+		return vertex_partition_map;
+	}
+	
+	/**
+	 * Returns a collection of vertex sets, where each vertex in the 
+	 * input graph is in exactly one set.
+	 * This collection is generated based on the vertex-to-partition map 
+	 * if it does not already exist.
+	 * @return a collection of vertex sets such that each vertex in the 
+	 * instance's graph is in exactly one set
+	 */
+	public Collection<Set<V>> getVertexPartitions() 
+	{
+		if (vertex_sets == null)
+		{
+			this.vertex_sets = new HashSet<Set<V>>();
+			this.vertex_sets.addAll(vertex_partition_map.values());
+		}
+	    return vertex_sets;
+	}
+
+	/**
+	 * @return the number of partitions.
+	 */
+	public int numPartitions() 
+	{
+		return vertex_sets.size();
+	}
+	
+	@Override
+  	public String toString() 
+	{
+		return "Partitions: " + vertex_partition_map;
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/blockmodel/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/blockmodel/package.html
new file mode 100644
index 0000000..62cde8d
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/blockmodel/package.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Support for establishing and maintaining graph element equivalence (such as in blockmodeling).
+<P/>
+In blockmodeling, groups of vertices are clustered together by similarity 
+(as if members of a "block" appearing on the diagonal of the graph's adjacency 
+matrix).
+<p/>
+This support currently includes:
+<ul>
+<li><code>VertexPartition</code>: A class that maintains information on a 
+division of the vertices of a graph into disjoint sets.
+<li><code>StructurallyEquivalent</code>: An algorithm that finds sets of vertices that are 
+structurally equivalent.
+</ul> 
+
+<p/>
+</body>
+</html>
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/BicomponentClusterer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/BicomponentClusterer.java
new file mode 100644
index 0000000..359f100
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/BicomponentClusterer.java
@@ -0,0 +1,165 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.cluster;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.UndirectedGraph;
+
+/**
+ * Finds all biconnected components (bicomponents) of an undirected graph.  
+ * A graph is a biconnected component if 
+ * at least 2 vertices must be removed in order to disconnect the graph.  (Graphs 
+ * consisting of one vertex, or of two connected vertices, are also biconnected.)  Biconnected
+ * components of three or more vertices have the property that every pair of vertices in the component
+ * are connected by two or more vertex-disjoint paths.
+ * <p>
+ * Running time: O(|V| + |E|) where |V| is the number of vertices and |E| is the number of edges
+ * @see "Depth first search and linear graph algorithms by R. E. Tarjan (1972), SIAM J. Comp."
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class BicomponentClusterer<V,E> implements Function<UndirectedGraph<V,E>, Set<Set<V>>> 
+{
+    protected Map<V,Number> dfs_num;
+    protected Map<V,Number> high;
+    protected Map<V,V> parents;
+    protected Stack<E> stack;
+    protected int converse_depth;
+
+    /**
+     * Constructs a new bicomponent finder
+     */
+    public BicomponentClusterer() {
+    }
+
+    /**
+    * Extracts the bicomponents from the graph.
+    * @param theGraph the graph whose bicomponents are to be extracted
+    * @return the <code>ClusterSet</code> of bicomponents
+    */
+    public Set<Set<V>> apply(UndirectedGraph<V,E> theGraph) 
+    {
+    	Set<Set<V>> bicomponents = new LinkedHashSet<Set<V>>();
+
+        if (theGraph.getVertices().isEmpty())
+            return bicomponents;
+
+        // initialize DFS number for each vertex to 0
+        dfs_num = new HashMap<V,Number>();
+        for (V v : theGraph.getVertices())
+        {
+        	dfs_num.put(v, 0);
+        }
+
+        for (V v : theGraph.getVertices())
+        {
+            if (dfs_num.get(v).intValue() == 0) // if we haven't hit this vertex yet...
+            {
+                high = new HashMap<V,Number>();
+                stack = new Stack<E>();
+                parents = new HashMap<V,V>();
+                converse_depth = theGraph.getVertexCount();
+                // find the biconnected components for this subgraph, starting from v
+                findBiconnectedComponents(theGraph, v, bicomponents);
+                
+                // if we only visited one vertex, this method won't have
+                // ID'd it as a biconnected component, so mark it as one
+                if (theGraph.getVertexCount() - converse_depth == 1)
+                {
+                    Set<V> s = new HashSet<V>();
+                    s.add(v);
+                    bicomponents.add(s);
+                }
+            }
+        }
+        
+        return bicomponents;
+    }
+
+    /**
+     * <p>Stores, in <code>bicomponents</code>, all the biconnected
+     * components that are reachable from <code>v</code>.
+     * 
+     * <p>The algorithm basically proceeds as follows: do a depth-first
+     * traversal starting from <code>v</code>, marking each vertex with
+     * a value that indicates the order in which it was encountered (dfs_num), 
+     * and with
+     * a value that indicates the highest point in the DFS tree that is known
+     * to be reachable from this vertex using non-DFS edges (high).  (Since it
+     * is measured on non-DFS edges, "high" tells you how far back in the DFS
+     * tree you can reach by two distinct paths, hence biconnectivity.) 
+     * Each time a new vertex w is encountered, push the edge just traversed
+     * on a stack, and call this method recursively.  If w.high is no greater than
+     * v.dfs_num, then the contents of the stack down to (v,w) is a 
+     * biconnected component (and v is an articulation point, that is, a 
+     * component boundary).  In either case, set v.high to max(v.high, w.high), 
+     * and continue.  If w has already been encountered but is 
+     * not v's parent, set v.high max(v.high, w.dfs_num) and continue. 
+     * 
+     * <p>(In case anyone cares, the version of this algorithm on p. 224 of 
+     * Udi Manber's "Introduction to Algorithms: A Creative Approach" seems to be
+     * wrong: the stack should be initialized outside this method, 
+     * (v,w) should only be put on the stack if w hasn't been seen already,
+     * and there's no real benefit to putting v on the stack separately: just
+     * check for (v,w) on the stack rather than v.  Had I known this, I could
+     * have saved myself a few days.  JRTOM)
+     * 
+     * @param g the graph to check for biconnected components
+     * @param v the starting place for searching for biconnected components
+     * @param bicomponents storage for the biconnected components found by this algorithm
+     */
+    protected void findBiconnectedComponents(UndirectedGraph<V,E> g, V v, Set<Set<V>> bicomponents)
+    {
+        int v_dfs_num = converse_depth;
+        dfs_num.put(v, v_dfs_num);
+        converse_depth--;
+        high.put(v, v_dfs_num);
+
+        for (V w : g.getNeighbors(v))
+        {
+            int w_dfs_num = dfs_num.get(w).intValue();//get(w, dfs_num);
+            E vw = g.findEdge(v,w);
+            if (w_dfs_num == 0) // w hasn't yet been visited
+            {
+                parents.put(w, v); // v is w's parent in the DFS tree
+                stack.push(vw);
+                findBiconnectedComponents(g, w, bicomponents);
+                int w_high = high.get(w).intValue();//get(w, high);
+                if (w_high <= v_dfs_num)
+                {
+                    // v disconnects w from the rest of the graph,
+                    // i.e., v is an articulation point
+                    // thus, everything between the top of the stack and
+                    // v is part of a single biconnected component
+                    Set<V> bicomponent = new HashSet<V>();
+                    E e;
+                    do
+                    {
+                        e = stack.pop();
+                        bicomponent.addAll(g.getIncidentVertices(e));
+                    }
+                    while (e != vw);
+                    bicomponents.add(bicomponent);
+                }
+                high.put(v, Math.max(w_high, high.get(v).intValue()));
+            }
+            else if (w != parents.get(v)) // (v,w) is a back or a forward edge
+            	high.put(v, Math.max(w_dfs_num, high.get(v).intValue()));
+        }
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/EdgeBetweennessClusterer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/EdgeBetweennessClusterer.java
new file mode 100644
index 0000000..80e30c0
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/EdgeBetweennessClusterer.java
@@ -0,0 +1,109 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.cluster;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.scoring.BetweennessCentrality;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+
+
+/**
+ * An algorithm for computing clusters (community structure) in graphs based on edge betweenness.
+ * The betweenness of an edge is defined as the extent to which that edge lies along 
+ * shortest paths between all pairs of nodes.
+ *
+ * This algorithm works by iteratively following the 2 step process:
+ * <ul>
+ * <li> Compute edge betweenness for all edges in current graph
+ * <li> Remove edge with highest betweenness
+ * </ul>
+ * <p>
+ * Running time is: O(kmn) where k is the number of edges to remove, m is the total number of edges, and
+ * n is the total number of vertices. For very sparse graphs the running time is closer to O(kn^2) and for
+ * graphs with strong community structure, the complexity is even lower.
+ * <p>
+ * This algorithm is a slight modification of the algorithm discussed below in that the number of edges
+ * to be removed is parameterized.
+ * @author Scott White
+ * @author Tom Nelson (converted to jung2)
+ * @see "Community structure in social and biological networks by Michelle Girvan and Mark Newman"
+ */
+public class EdgeBetweennessClusterer<V,E> implements Function<Graph<V,E>,Set<Set<V>>> {
+    private int mNumEdgesToRemove;
+    private Map<E, Pair<V>> edges_removed;
+
+   /**
+    * Constructs a new clusterer for the specified graph.
+    * @param numEdgesToRemove the number of edges to be progressively removed from the graph
+    */
+    public EdgeBetweennessClusterer(int numEdgesToRemove) {
+        mNumEdgesToRemove = numEdgesToRemove;
+        edges_removed = new LinkedHashMap<E, Pair<V>>();
+    }
+
+    /**
+    * Finds the set of clusters which have the strongest "community structure".
+    * The more edges removed the smaller and more cohesive the clusters.
+    * @param graph the graph
+    */
+    public Set<Set<V>> apply(Graph<V,E> graph) {
+                
+        if (mNumEdgesToRemove < 0 || mNumEdgesToRemove > graph.getEdgeCount()) {
+            throw new IllegalArgumentException("Invalid number of edges passed in.");
+        }
+        
+        edges_removed.clear();
+
+        for (int k=0;k<mNumEdgesToRemove;k++) {
+            BetweennessCentrality<V,E> bc = new BetweennessCentrality<V,E>(graph);
+            E to_remove = null;
+            double score = 0;
+            for (E e : graph.getEdges())
+                if (bc.getEdgeScore(e) > score)
+                {
+                    to_remove = e;
+                    score = bc.getEdgeScore(e);
+                }
+            edges_removed.put(to_remove, graph.getEndpoints(to_remove));
+            graph.removeEdge(to_remove);
+        }
+
+        WeakComponentClusterer<V,E> wcSearch = new WeakComponentClusterer<V,E>();
+        Set<Set<V>> clusterSet = wcSearch.apply(graph);
+
+        for (Map.Entry<E, Pair<V>> entry : edges_removed.entrySet())
+        {
+            Pair<V> endpoints = entry.getValue();
+            graph.addEdge(entry.getKey(), endpoints.getFirst(), endpoints.getSecond());
+        }
+        return clusterSet;
+    }
+
+    /**
+     * Retrieves the list of all edges that were removed 
+     * (assuming extract(...) was previously called).  
+     * The edges returned
+     * are stored in order in which they were removed.
+     * 
+     * @return the edges in the original graph
+     */
+    public List<E> getEdgesRemoved() 
+    {
+        return new ArrayList<E>(edges_removed.keySet());
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/VoltageClusterer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/VoltageClusterer.java
new file mode 100644
index 0000000..cc2abf8
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/VoltageClusterer.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 2004, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 12, 2004
+ */
+package edu.uci.ics.jung.algorithms.cluster;
+
+import edu.uci.ics.jung.algorithms.scoring.VoltageScorer;
+import edu.uci.ics.jung.algorithms.util.DiscreteDistribution;
+import edu.uci.ics.jung.algorithms.util.KMeansClusterer;
+import edu.uci.ics.jung.algorithms.util.KMeansClusterer.NotEnoughClustersException;
+import edu.uci.ics.jung.graph.Graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+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 java.util.Random;
+import java.util.Set;
+
+/**
+ * <p>Clusters vertices of a <code>Graph</code> based on their ranks as
+ * calculated by <code>VoltageScorer</code>.  This algorithm is based on,
+ * but not identical with, the method described in the paper below.
+ * The primary difference is that Wu and Huberman assume a priori that the clusters
+ * are of approximately the same size, and therefore use a more complex
+ * method than k-means (which is used here) for determining cluster
+ * membership based on co-occurrence data.
+ *
+ * <p>The algorithm proceeds as follows:
+ * <ul>
+ * <li>first, generate a set of candidate clusters as follows:
+ *      <ul>
+ *      <li>pick (widely separated) vertex pair, run VoltageScorer
+ *      <li>group the vertices in two clusters according to their voltages
+ *      <li>store resulting candidate clusters
+ *      </ul>
+ * <li>second, generate k-1 clusters as follows:
+ *      <ul>
+ *      <li>pick a vertex v as a cluster 'seed'
+ *           <br>(Wu/Huberman: most frequent vertex in candidate clusters)
+ *      <li>calculate co-occurrence over all candidate clusters of v with each other
+ *           vertex
+ *      <li>separate co-occurrence counts into high/low;
+ *           high vertices constitute a cluster
+ *      <li>remove v's vertices from candidate clusters; continue
+ *      </ul>
+ * <li>finally, remaining unassigned vertices are assigned to the kth ("garbage")
+ * cluster.
+ * </ul>
+ *
+ * <p><b>NOTE</b>: Depending on how the co-occurrence data splits the data into
+ * clusters, the number of clusters returned by this algorithm may be less than the
+ * number of clusters requested.  The number of clusters will never be more than
+ * the number requested, however.
+ *
+ * @author Joshua O'Madadhain
+ * @see "'Finding communities in linear time: a physics approach', Fang Wu and Bernardo Huberman, http://www.hpl.hp.com/research/idl/papers/linear/"
+ * @see VoltageScorer
+ * @see KMeansClusterer
+ */
+public class VoltageClusterer<V,E>
+{
+    protected int num_candidates;
+    protected KMeansClusterer<V> kmc;
+    protected Random rand;
+    protected Graph<V,E> g;
+
+    /**
+     * Creates an instance of a VoltageCluster with the specified parameters.
+     * These are mostly parameters that are passed directly to VoltageScorer
+     * and KMeansClusterer.
+     * 
+     * @param g the graph whose vertices are to be clustered
+     * @param num_candidates    the number of candidate clusters to create
+     */
+    public VoltageClusterer(Graph<V,E> g, int num_candidates)
+    {
+        if (num_candidates < 1)
+            throw new IllegalArgumentException("must generate >=1 candidates");
+
+        this.num_candidates = num_candidates;
+        this.kmc = new KMeansClusterer<V>();
+        rand = new Random();
+        this.g = g;
+    }
+
+    protected void setRandomSeed(int random_seed)
+    {
+        rand = new Random(random_seed);
+    }
+
+    /**
+     * @param v the vertex whose community we wish to discover
+     * @return a community (cluster) centered around <code>v</code>.
+     */
+    public Collection<Set<V>> getCommunity(V v)
+    {
+        return cluster_internal(v, 2);
+    }
+
+    /**
+     * Clusters the vertices of <code>g</code> into
+     * <code>num_clusters</code> clusters, based on their connectivity.
+     * @param num_clusters  the number of clusters to identify
+     * @return a collection of clusters (sets of vertices)
+     */
+    public Collection<Set<V>> cluster(int num_clusters)
+    {
+        return cluster_internal(null, num_clusters);
+    }
+
+    /**
+     * Does the work of <code>getCommunity</code> and <code>cluster</code>.
+     * @param origin the vertex around which clustering is to be done
+     * @param num_clusters the (maximum) number of clusters to find
+     * @return a collection of clusters (sets of vertices)
+     */
+    protected Collection<Set<V>> cluster_internal(V origin, int num_clusters)
+    {
+        // generate candidate clusters
+        // repeat the following 'samples' times:
+        // * pick (widely separated) vertex pair, run VoltageScorer
+        // * use k-means to identify 2 communities in ranked graph
+        // * store resulting candidate communities
+        ArrayList<V> v_array = new ArrayList<V>(g.getVertices());
+
+        LinkedList<Set<V>> candidates = new LinkedList<Set<V>>();
+
+        for (int j = 0; j < num_candidates; j++)
+        {
+            V source;
+            if (origin == null)
+                source = v_array.get((int)(rand.nextDouble() * v_array.size()));
+            else
+                source = origin;
+            V target = null;
+            do
+            {
+                target = v_array.get((int)(rand.nextDouble() * v_array.size()));
+            }
+            while (source == target);
+            VoltageScorer<V,E> vs = new VoltageScorer<V,E>(g, source, target);
+            vs.evaluate();
+
+            Map<V, double[]> voltage_ranks = new HashMap<V, double[]>();
+            for (V v : g.getVertices())
+                voltage_ranks.put(v, new double[] {vs.getVertexScore(v)});
+
+//            addOneCandidateCluster(candidates, voltage_ranks);
+            addTwoCandidateClusters(candidates, voltage_ranks);
+        }
+
+        // repeat the following k-1 times:
+        // * pick a vertex v as a cluster seed
+        //   (Wu/Huberman: most frequent vertex in candidates)
+        // * calculate co-occurrence (in candidate clusters)
+        //   of this vertex with all others
+        // * use k-means to separate co-occurrence counts into high/low;
+        //   high vertices are a cluster
+        // * remove v's vertices from candidate clusters
+
+        Collection<Set<V>> clusters = new LinkedList<Set<V>>();
+        Set<V> remaining = new HashSet<V>(g.getVertices());
+
+        List<V> seed_candidates = getSeedCandidates(candidates);
+        int seed_index = 0;
+
+        for (int j = 0; j < (num_clusters - 1); j++)
+        {
+            if (remaining.isEmpty())
+                break;
+
+            V seed;
+            if (seed_index == 0 && origin != null)
+                seed = origin;
+            else
+            {
+                do { seed = seed_candidates.get(seed_index++); }
+                while (!remaining.contains(seed));
+            }
+
+            Map<V, double[]> occur_counts = getObjectCounts(candidates, seed);
+            if (occur_counts.size() < 2)
+                break;
+
+            // now that we have the counts, cluster them...
+            try
+            {
+                Collection<Map<V, double[]>> high_low = kmc.cluster(occur_counts, 2);
+                // ...get the cluster with the highest-valued centroid...
+                Iterator<Map<V, double[]>> h_iter = high_low.iterator();
+                Map<V, double[]> cluster1 = h_iter.next();
+                Map<V, double[]> cluster2 = h_iter.next();
+                double[] centroid1 = DiscreteDistribution.mean(cluster1.values());
+                double[] centroid2 = DiscreteDistribution.mean(cluster2.values());
+                Set<V> new_cluster;
+                if (centroid1[0] >= centroid2[0])
+                    new_cluster = cluster1.keySet();
+                else
+                    new_cluster = cluster2.keySet();
+
+                // ...remove the elements of new_cluster from each candidate...
+                for (Set<V> cluster : candidates)
+                    cluster.removeAll(new_cluster);
+                clusters.add(new_cluster);
+                remaining.removeAll(new_cluster);
+            }
+            catch (NotEnoughClustersException nece)
+            {
+                // all remaining vertices are in the same cluster
+                break;
+            }
+        }
+
+        // identify remaining vertices (if any) as a 'garbage' cluster
+        if (!remaining.isEmpty())
+            clusters.add(remaining);
+
+        return clusters;
+    }
+
+    /**
+     * Do k-means with three intervals and pick the smaller two clusters 
+     * (presumed to be on the ends); this is closer to the Wu-Huberman method.
+     * @param candidates the list of clusters to populate
+     * @param voltage_ranks the voltage values for each vertex
+     */
+    protected void addTwoCandidateClusters(LinkedList<Set<V>> candidates,
+            Map<V, double[]> voltage_ranks)
+    {
+        try
+        {
+            List<Map<V, double[]>> clusters = new ArrayList<Map<V, double[]>>(kmc.cluster(voltage_ranks, 3));
+            boolean b01 = clusters.get(0).size() > clusters.get(1).size();
+            boolean b02 = clusters.get(0).size() > clusters.get(2).size();
+            boolean b12 = clusters.get(1).size() > clusters.get(2).size();
+            if (b01 && b02)
+            {
+                candidates.add(clusters.get(1).keySet());
+                candidates.add(clusters.get(2).keySet());
+            }
+            else if (!b01 && b12)
+            {
+                candidates.add(clusters.get(0).keySet());
+                candidates.add(clusters.get(2).keySet());
+            }
+            else if (!b02 && !b12)
+            {
+                candidates.add(clusters.get(0).keySet());
+                candidates.add(clusters.get(1).keySet());
+            }
+        }
+        catch (NotEnoughClustersException e)
+        {
+            // no valid candidates, continue
+        }
+    }
+
+    /**
+     * alternative to addTwoCandidateClusters(): cluster vertices by voltages into 2 clusters.
+     * We only consider the smaller of the two clusters returned
+     * by k-means to be a 'true' cluster candidate; the other is a garbage cluster.
+     * @param candidates the list of clusters to populate
+     * @param voltage_ranks the voltage values for each vertex
+     */
+    protected void addOneCandidateCluster(LinkedList<Set<V>> candidates,
+            Map<V, double[]> voltage_ranks)
+    {
+        try
+        {
+            List<Map<V, double[]>> clusters;
+            clusters = new ArrayList<Map<V, double[]>>(kmc.cluster(voltage_ranks, 2));
+            if (clusters.get(0).size() < clusters.get(1).size())
+                candidates.add(clusters.get(0).keySet());
+            else
+                candidates.add(clusters.get(1).keySet());
+        }
+        catch (NotEnoughClustersException e)
+        {
+            // no valid candidates, continue
+        }
+    }
+
+    /**
+     * Returns a list of cluster seeds, ranked in decreasing order
+     * of number of appearances in the specified collection of candidate
+     * clusters.
+     * @param candidates the set of candidate clusters
+     * @return a set of cluster seeds
+     */
+    protected List<V> getSeedCandidates(Collection<Set<V>> candidates)
+    {
+        final Map<V, double[]> occur_counts = getObjectCounts(candidates, null);
+
+        ArrayList<V> occurrences = new ArrayList<V>(occur_counts.keySet());
+        Collections.sort(occurrences, new MapValueArrayComparator(occur_counts));
+
+//        System.out.println("occurrences: ");
+        for (int i = 0; i < occurrences.size(); i++)
+            System.out.println(occur_counts.get(occurrences.get(i))[0]);
+
+        return occurrences;
+    }
+
+    protected Map<V, double[]> getObjectCounts(Collection<Set<V>> candidates, V seed)
+    {
+        Map<V, double[]> occur_counts = new HashMap<V, double[]>();
+        for (V v : g.getVertices())
+            occur_counts.put(v, new double[]{0});
+
+        for (Set<V> candidate : candidates)
+        {
+            if (seed == null)
+                System.out.println(candidate.size());
+            if (seed == null || candidate.contains(seed))
+            {
+                for (V element : candidate)
+                {
+                    double[] count = occur_counts.get(element);
+                    count[0]++;
+                }
+            }
+        }
+
+        if (seed == null)
+        {
+            System.out.println("occur_counts size: " + occur_counts.size());
+            for (V v : occur_counts.keySet())
+                System.out.println(occur_counts.get(v)[0]);
+        }
+
+        return occur_counts;
+    }
+
+    protected class MapValueArrayComparator implements Comparator<V>
+    {
+        private Map<V, double[]> map;
+
+        protected MapValueArrayComparator(Map<V, double[]> map)
+        {
+            this.map = map;
+        }
+
+        public int compare(V o1, V o2)
+        {
+            double[] count0 = map.get(o1);
+            double[] count1 = map.get(o2);
+            if (count0[0] < count1[0])
+                return 1;
+            else if (count0[0] > count1[0])
+                return -1;
+            return 0;
+        }
+
+    }
+
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/WeakComponentClusterer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/WeakComponentClusterer.java
new file mode 100644
index 0000000..310cfda
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/WeakComponentClusterer.java
@@ -0,0 +1,73 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.cluster;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.Set;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Graph;
+
+
+
+/**
+ * Finds all weak components in a graph as sets of vertex sets.  A weak component is defined as
+ * a maximal subgraph in which all pairs of vertices in the subgraph are reachable from one
+ * another in the underlying undirected subgraph.
+ * <p>This implementation identifies components as sets of vertex sets.  
+ * To create the induced graphs from any or all of these vertex sets, 
+ * see <code>algorithms.filters.FilterUtils</code>.
+ * <p>
+ * Running time: O(|V| + |E|) where |V| is the number of vertices and |E| is the number of edges.
+ * @author Scott White
+ */
+public class WeakComponentClusterer<V,E> implements Function<Graph<V,E>, Set<Set<V>>> 
+{
+	/**
+     * Extracts the weak components from a graph.
+     * @param graph the graph whose weak components are to be extracted
+     * @return the list of weak components
+     */
+    public Set<Set<V>> apply(Graph<V,E> graph) {
+
+        Set<Set<V>> clusterSet = new HashSet<Set<V>>();
+
+        HashSet<V> unvisitedVertices = new HashSet<V>(graph.getVertices());
+
+        while (!unvisitedVertices.isEmpty()) {
+        	Set<V> cluster = new HashSet<V>();
+            V root = unvisitedVertices.iterator().next();
+            unvisitedVertices.remove(root);
+            cluster.add(root);
+
+            Queue<V> queue = new LinkedList<V>();
+            queue.add(root);
+
+            while (!queue.isEmpty()) {
+                V currentVertex = queue.remove();
+                Collection<V> neighbors = graph.getNeighbors(currentVertex);
+
+                for(V neighbor : neighbors) {
+                    if (unvisitedVertices.contains(neighbor)) {
+                        queue.add(neighbor);
+                        unvisitedVertices.remove(neighbor);
+                        cluster.add(neighbor);
+                    }
+                }
+            }
+            clusterSet.add(cluster);
+        }
+        return clusterSet;
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/package.html
new file mode 100644
index 0000000..1663029
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/cluster/package.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Mechanisms for identifying clusters in graphs.  Where these clusters define disjoint sets of vertices, 
+they may be used to define a <code>VertexPartition</code> for more convenient manipulation of the vertex/set
+relationships.
+
+Current clustering algorithms include:
+<ul>
+<li><code>BicomponentClusterer</code>: finds all subsets of vertices for which at least
+2 vertices must be removed in order to disconnect the induced subgraphs.
+<li><code>EdgeBetweennessClusterer</code>: identifies vertex clusters by removing the edges of the highest
+'betweenness' scores (see the importance/scoring package).
+<li><code>VoltageClusterer</code>: Clusters vertices based on their ranks as 
+calculated by <code>VoltageRanker</code>. 
+<li><code>WeakComponentVertexClusterer</code>: Clusters vertices based on their membership in weakly 
+connected components of a graph.
+</ul>
+
+
+</body>
+</html>
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/EdgePredicateFilter.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/EdgePredicateFilter.java
new file mode 100644
index 0000000..7c474c3
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/EdgePredicateFilter.java
@@ -0,0 +1,70 @@
+/*
+ * Created on May 19, 2008
+ *
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.filters;
+
+import com.google.common.base.Predicate;
+
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * Transforms the input graph into one which contains only those edges 
+ * that pass the specified <code>Predicate</code>.  The filtered graph
+ * is a copy of the original graph (same type, uses the same vertex and
+ * edge objects).  All vertices from the original graph
+ * are copied into the new graph (even if they are not incident to any
+ * edges in the new graph).
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class EdgePredicateFilter<V, E> implements Filter<V, E>
+{
+    protected Predicate<E> edge_pred;
+
+    /**
+     * Creates an instance based on the specified edge <code>Predicate</code>.
+     * @param edge_pred   the predicate that specifies which edges to add to the filtered graph
+     */
+    public EdgePredicateFilter(Predicate<E> edge_pred)
+    {
+        this.edge_pred = edge_pred;
+    }
+    
+    @SuppressWarnings("unchecked")
+    public Graph<V,E> apply(Graph<V,E> g)
+    {
+        Graph<V, E> filtered;
+        try
+        {
+            filtered = g.getClass().newInstance();
+        }
+        catch (InstantiationException e)
+        {
+            throw new RuntimeException("Unable to create copy of existing graph: ", e);
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new RuntimeException("Unable to create copy of existing graph: ", e);
+        }
+
+        for (V v : g.getVertices())
+            filtered.addVertex(v);
+        
+        for (E e : g.getEdges())
+        {
+            if (edge_pred.apply(e))
+                filtered.addEdge(e, g.getIncidentVertices(e));
+        }
+        
+        return filtered;
+    }
+
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/Filter.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/Filter.java
new file mode 100644
index 0000000..48e5da0
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/Filter.java
@@ -0,0 +1,26 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.filters;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Graph;
+
+
+
+/**
+ * An interface for classes that return a subset of the input <code>Graph</code>
+ * as a <code>Graph</code>.  The <code>Graph</code> returned may be either a
+ * new graph or a view into an existing graph; the documentation for the filter
+ * must specify which.
+ * 
+ * @author danyelf
+ */
+public interface Filter<V,E> extends Function<Graph<V,E>, Graph<V,E>>{ }
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/FilterUtils.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/FilterUtils.java
new file mode 100644
index 0000000..671b8af
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/FilterUtils.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Jun 7, 2008
+ * 
+ */
+package edu.uci.ics.jung.algorithms.filters;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * Utility methods relating to filtering.
+ */
+public class FilterUtils 
+{
+	/**
+	 * Creates the induced subgraph from <code>graph</code> whose vertex set
+	 * is equal to <code>vertices</code>.  The graph returned has 
+	 * <code>vertices</code> as its vertex set, and includes all edges from
+	 * <code>graph</code> which are incident only to elements of 
+	 * <code>vertices</code>.
+	 * 
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+	 * @param <G> the graph type
+	 * @param vertices the subset of <code>graph</code>'s vertices around 
+	 * which the subgraph is to be constructed
+	 * @param graph the graph whose subgraph is to be constructed
+	 * @return the subgraph induced by <code>vertices</code>
+	 * @throws IllegalArgumentException if any vertex in 
+	 * <code>vertices</code> is not in <code>graph</code>
+	 */
+	@SuppressWarnings("unchecked")
+	public static <V,E,G extends Hypergraph<V,E>> G createInducedSubgraph(Collection<V> 
+		vertices, G graph)
+	{
+		G subgraph = null;
+		try 
+		{
+			subgraph = (G)graph.getClass().newInstance();
+			
+			for (V v : vertices)
+			{
+				if (!graph.containsVertex(v))
+					throw new IllegalArgumentException("Vertex " + v + 
+						" is not an element of " + graph);
+				subgraph.addVertex(v);
+			}
+
+			for (E e : graph.getEdges())
+			{
+				Collection<V> incident = graph.getIncidentVertices(e);
+				if (vertices.containsAll(incident))
+					subgraph.addEdge(e, incident, graph.getEdgeType(e));
+			}
+		} 
+        catch (InstantiationException e)
+        {
+            throw new RuntimeException("Unable to create copy of existing graph: ", e);
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new RuntimeException("Unable to create copy of existing graph: ", e);
+        }
+		return subgraph;
+	}
+	
+	/**
+	 * Creates the induced subgraphs of <code>graph</code> associated with each 
+	 * element of <code>vertex_collections</code>.
+	 * Note that these vertex collections need not be disjoint.
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+	 * @param <G> the graph type
+	 * @param vertex_collections the collections of vertex collections to be
+	 * used to induce the subgraphs
+	 * @param graph the graph whose subgraphs are to be created
+	 * @return the induced subgraphs of <code>graph</code> associated with each 
+	 * element of <code>vertex_collections</code>
+	 */
+	public static <V,E,G extends Hypergraph<V,E>> Collection<G> 
+		createAllInducedSubgraphs(Collection<? extends Collection<V>> 
+			vertex_collections, G graph)
+	{
+		Collection<G> subgraphs = new ArrayList<G>();
+		
+		for (Collection<V> vertex_set : vertex_collections)
+			subgraphs.add(createInducedSubgraph(vertex_set, graph));
+		
+		return subgraphs;
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/KNeighborhoodFilter.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/KNeighborhoodFilter.java
new file mode 100644
index 0000000..9f189ab
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/KNeighborhoodFilter.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Dec 26, 2001
+ *
+ */
+package edu.uci.ics.jung.algorithms.filters;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import edu.uci.ics.jung.algorithms.filters.Filter;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * A filter used to extract the k-neighborhood around one or more root node(s).
+ * The k-neighborhood is defined as the subgraph induced by the set of 
+ * vertices that are k or fewer hops (unweighted shortest-path distance)
+ * away from the root node.
+ * 
+ * @author Danyel Fisher
+ */
+public class KNeighborhoodFilter<V,E> implements Filter<V,E> {
+
+	/**
+	 * The type of edge to follow for defining the neighborhood.
+	 */
+	public static enum EdgeType { IN_OUT, IN, OUT }
+	private Set<V> rootNodes;
+	private int radiusK;
+	private EdgeType edgeType;
+	
+	/**
+	 * Constructs a new instance of the filter.
+	 * @param rootNodes the set of root nodes
+	 * @param radiusK the neighborhood radius around the root set
+	 * @param edgeType 0 for in/out edges, 1 for in-edges, 2  for out-edges
+	 */
+	public KNeighborhoodFilter(Set<V> rootNodes, int radiusK, EdgeType edgeType) {
+		this.rootNodes = rootNodes;
+		this.radiusK = radiusK;
+		this.edgeType = edgeType;
+	}
+	
+	/**
+	 * Constructs a new instance of the filter.
+	 * @param rootNode the root node
+	 * @param radiusK the neighborhood radius around the root set
+	 * @param edgeType 0 for in/out edges, 1 for in-edges, 2  for out-edges
+	 */
+	public KNeighborhoodFilter(V rootNode, int radiusK, EdgeType edgeType) {
+		this.rootNodes = new HashSet<V>();
+		this.rootNodes.add(rootNode);
+		this.radiusK = radiusK;
+		this.edgeType = edgeType;
+	}
+	
+	/**
+	 * Constructs an unassembled graph containing the k-neighborhood around the root node(s).
+	 */
+	@SuppressWarnings("unchecked")
+	public Graph<V,E> apply(Graph<V,E> graph) {
+		// generate a Set of Vertices we want
+		// add all to the UG
+		int currentDepth = 0;
+		List<V> currentVertices = new ArrayList<V>();
+		Set<V> visitedVertices = new HashSet<V>();
+		Set<E> visitedEdges = new HashSet<E>();
+		Set<V> acceptedVertices = new HashSet<V>();
+		//Copy, mark, and add all the root nodes to the new subgraph
+		for (V currentRoot : rootNodes) {
+
+			visitedVertices.add(currentRoot);
+			acceptedVertices.add(currentRoot);
+			currentVertices.add(currentRoot);
+		}
+		ArrayList<V> newVertices = null;
+		//Use BFS to locate the neighborhood around the root nodes within distance k
+		while (currentDepth < radiusK) {
+			newVertices = new ArrayList<V>();
+			for (V currentVertex : currentVertices) {
+
+				Collection<E> edges = null;
+				switch (edgeType) {
+					case IN_OUT :
+						edges = graph.getIncidentEdges(currentVertex);
+						break;
+					case IN :
+						edges = graph.getInEdges(currentVertex);
+						break;
+					case OUT :
+						edges = graph.getOutEdges(currentVertex);
+						break;
+				}
+				for (E currentEdge : edges) {
+
+					V currentNeighbor =
+						graph.getOpposite(currentVertex, currentEdge);
+					if (!visitedEdges.contains(currentEdge)) {
+						visitedEdges.add(currentEdge);
+						if (!visitedVertices.contains(currentNeighbor)) {
+							visitedVertices.add(currentNeighbor);
+							acceptedVertices.add(currentNeighbor);
+							newVertices.add(currentNeighbor);
+						}
+					}
+				}
+			}
+			currentVertices = newVertices;
+			currentDepth++;
+		}
+		Graph<V,E> ug = null;
+		try {
+			ug = graph.getClass().newInstance();
+			for(E edge : graph.getEdges()) {
+				Pair<V> endpoints = graph.getEndpoints(edge);
+				if(acceptedVertices.containsAll(endpoints)) {
+					ug.addEdge(edge, endpoints.getFirst(), endpoints.getSecond());
+				}
+			}
+		} 
+        catch (InstantiationException e)
+        {
+            throw new RuntimeException("Unable to create copy of existing graph: ", e);
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new RuntimeException("Unable to create copy of existing graph: ", e);
+        }
+		return ug;
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/VertexPredicateFilter.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/VertexPredicateFilter.java
new file mode 100644
index 0000000..81a9d0e
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/VertexPredicateFilter.java
@@ -0,0 +1,75 @@
+/*
+ * Created on May 19, 2008
+ *
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.filters;
+
+import java.util.Collection;
+
+import com.google.common.base.Predicate;
+
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * Transforms the input graph into one which contains only those vertices 
+ * that pass the specified <code>Predicate</code>.  The filtered graph
+ * is a copy of the original graph (same type, uses the same vertex and
+ * edge objects).  Only those edges whose entire incident vertex collection
+ * passes the predicate are copied into the new graph.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class VertexPredicateFilter<V,E> implements Filter<V,E>
+{
+    protected Predicate<V> vertex_pred;
+
+    /**
+     * Creates an instance based on the specified vertex <code>Predicate</code>.
+     * @param vertex_pred   the predicate that specifies which vertices to add to the filtered graph
+     */
+    public VertexPredicateFilter(Predicate<V> vertex_pred)
+    {
+        this.vertex_pred = vertex_pred;
+    }
+    
+    @SuppressWarnings("unchecked")
+	public Graph<V,E> apply(Graph<V,E> g)
+    {
+        Graph<V, E> filtered;
+        try
+        {
+            filtered = g.getClass().newInstance();
+        }
+        catch (InstantiationException e)
+        {
+            throw new RuntimeException("Unable to create copy of existing graph: ", e);
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new RuntimeException("Unable to create copy of existing graph: ", e);
+        }
+
+        for (V v : g.getVertices())
+            if (vertex_pred.apply(v))
+                filtered.addVertex(v);
+        
+        Collection<V> filtered_vertices = filtered.getVertices();
+        
+        for (E e : g.getEdges())
+        {
+            Collection<V> incident = g.getIncidentVertices(e);
+            if (filtered_vertices.containsAll(incident))
+                filtered.addEdge(e, incident);
+        }
+        
+        return filtered;
+    }
+
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/package.html
new file mode 100644
index 0000000..581b211
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/filters/package.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Filtering mechanisms that produce subgraphs of an original graph. 
+Currently includes:
+<ul>
+<li><code>Filter</code>: an interface for graph filters
+<li><code>{Edge,Vertex}PredicateFilter</code>: graph filters that return the 
+induced subgraph according to the
+specified edge or vertex <code>Predicate</code>, respectively.
+<li><code>KNeighborhoodFilter</code>: a filter that returns the subgraph 
+induced by vertices within (unweighted) distance k of a specified vertex.
+</ul>
+
+
+</body>
+</html>
+
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/flows/EdmondsKarpMaxFlow.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/flows/EdmondsKarpMaxFlow.java
new file mode 100644
index 0000000..6653469
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/flows/EdmondsKarpMaxFlow.java
@@ -0,0 +1,314 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.flows;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.util.IterativeProcess;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+
+/**
+ * Implements the Edmonds-Karp maximum flow algorithm for solving the maximum flow problem. 
+ * After the algorithm is executed,
+ * the input {@code Map} is populated with a {@code Number} for each edge that indicates 
+ * the flow along that edge.
+ * <p>
+ * An example of using this algorithm is as follows:
+ * <pre>
+ * EdmondsKarpMaxFlow ek = new EdmondsKarpMaxFlow(graph, source, sink, edge_capacities, edge_flows, 
+ * edge_factory);
+ * ek.evaluate(); // This instructs the class to compute the max flow
+ * </pre>
+ *
+ * @see "Introduction to Algorithms by Cormen, Leiserson, Rivest, and Stein."
+ * @see "Network Flows by Ahuja, Magnanti, and Orlin."
+ * @see "Theoretical improvements in algorithmic efficiency for network flow problems by Edmonds and Karp, 1972."
+ * @author Scott White, adapted to jung2 by Tom Nelson
+ */
+public class EdmondsKarpMaxFlow<V,E> extends IterativeProcess {
+
+    private DirectedGraph<V,E> mFlowGraph;
+    private DirectedGraph<V,E> mOriginalGraph;
+    private V source;
+    private V target;
+    private int mMaxFlow;
+    private Set<V> mSourcePartitionNodes;
+    private Set<V> mSinkPartitionNodes;
+    private Set<E> mMinCutEdges;
+    
+    private Map<E,Number> residualCapacityMap = new HashMap<E,Number>();
+    private Map<V,V> parentMap = new HashMap<V,V>();
+    private Map<V,Number> parentCapacityMap = new HashMap<V,Number>();
+    private Function<E,Number> edgeCapacityTransformer;
+    private Map<E,Number> edgeFlowMap;
+    private Supplier<E> edgeFactory;
+
+    /**
+     * Constructs a new instance of the algorithm solver for a given graph, source, and sink.
+     * Source and sink vertices must be elements of the specified graph, and must be 
+     * distinct.
+     * @param directedGraph the flow graph
+     * @param source the source vertex
+     * @param sink the sink vertex
+     * @param edgeCapacityTransformer the Function that gets the capacity for each edge.
+     * @param edgeFlowMap the map where the solver will place the value of the flow for each edge
+     * @param edgeFactory used to create new edge instances for backEdges
+     */
+    @SuppressWarnings("unchecked")
+    public EdmondsKarpMaxFlow(DirectedGraph<V,E> directedGraph, V source, V sink, 
+    		Function<E,Number> edgeCapacityTransformer, Map<E,Number> edgeFlowMap,
+    		Supplier<E> edgeFactory) {
+    	
+    	if(directedGraph.getVertices().contains(source) == false ||
+    			directedGraph.getVertices().contains(sink) == false) {
+            throw new IllegalArgumentException("source and sink vertices must be elements of the specified graph");
+    	}
+        if (source.equals(sink)) {
+            throw new IllegalArgumentException("source and sink vertices must be distinct");
+        }
+        mOriginalGraph = directedGraph;
+
+        this.source = source;
+        this.target = sink;
+        this.edgeFlowMap = edgeFlowMap;
+        this.edgeCapacityTransformer = edgeCapacityTransformer;
+        this.edgeFactory = edgeFactory;
+        try {
+			mFlowGraph = directedGraph.getClass().newInstance();
+			for(E e : mOriginalGraph.getEdges()) {
+				mFlowGraph.addEdge(e, mOriginalGraph.getSource(e), 
+						mOriginalGraph.getDest(e), EdgeType.DIRECTED);
+			}
+			for(V v : mOriginalGraph.getVertices()) {
+				mFlowGraph.addVertex(v);
+			}
+
+		} catch (InstantiationException e) {
+			e.printStackTrace();
+		} catch (IllegalAccessException e) {
+			e.printStackTrace();
+		}
+        mMaxFlow = 0;
+        mSinkPartitionNodes = new HashSet<V>();
+        mSourcePartitionNodes = new HashSet<V>();
+        mMinCutEdges = new HashSet<E>();
+    }
+
+    private void clearParentValues() {
+    	parentMap.clear();
+    	parentCapacityMap.clear();
+        parentCapacityMap.put(source, Integer.MAX_VALUE);
+        parentMap.put(source, source);
+    }
+
+    protected boolean hasAugmentingPath() {
+
+        mSinkPartitionNodes.clear();
+        mSourcePartitionNodes.clear();
+        mSinkPartitionNodes.addAll(mFlowGraph.getVertices());
+
+        Set<E> visitedEdgesMap = new HashSet<E>();
+        Queue<V> queue = new LinkedList<V>();
+        queue.add(source);
+
+        while (!queue.isEmpty()) {
+            V currentVertex = queue.remove();
+            mSinkPartitionNodes.remove(currentVertex);
+            mSourcePartitionNodes.add(currentVertex);
+            Number currentCapacity = parentCapacityMap.get(currentVertex);
+
+            Collection<E> neighboringEdges = mFlowGraph.getOutEdges(currentVertex);
+            
+            for (E neighboringEdge : neighboringEdges) {
+
+                V neighboringVertex = mFlowGraph.getDest(neighboringEdge);
+
+                Number residualCapacity = residualCapacityMap.get(neighboringEdge);
+                if (residualCapacity.intValue() <= 0 || visitedEdgesMap.contains(neighboringEdge))
+                    continue;
+
+                V neighborsParent = parentMap.get(neighboringVertex);
+                Number neighborCapacity = parentCapacityMap.get(neighboringVertex);
+                int newCapacity = Math.min(residualCapacity.intValue(),currentCapacity.intValue());
+
+                if ((neighborsParent == null) || newCapacity > neighborCapacity.intValue()) {
+                    parentMap.put(neighboringVertex, currentVertex);
+                    parentCapacityMap.put(neighboringVertex, new Integer(newCapacity));
+                    visitedEdgesMap.add(neighboringEdge);
+                    if (neighboringVertex != target) {
+                       queue.add(neighboringVertex);
+                    }
+                }
+            }
+        }
+
+        boolean hasAugmentingPath = false;
+        Number targetsParentCapacity = parentCapacityMap.get(target);
+        if (targetsParentCapacity != null && targetsParentCapacity.intValue() > 0) {
+            updateResidualCapacities();
+            hasAugmentingPath = true;
+        }
+        clearParentValues();
+        return hasAugmentingPath;
+    }
+
+     @Override
+    public void step() {
+        while (hasAugmentingPath()) {
+        }
+        computeMinCut();
+//        return 0;
+    }
+
+    private void computeMinCut() {
+
+        for (E e : mOriginalGraph.getEdges()) {
+
+        	V source = mOriginalGraph.getSource(e);
+        	V destination = mOriginalGraph.getDest(e);
+            if (mSinkPartitionNodes.contains(source) && mSinkPartitionNodes.contains(destination)) {
+                continue;
+            }
+            if (mSourcePartitionNodes.contains(source) && mSourcePartitionNodes.contains(destination)) {
+                continue;
+            }
+            if (mSinkPartitionNodes.contains(source) && mSourcePartitionNodes.contains(destination)) {
+                continue;
+            }
+            mMinCutEdges.add(e);
+        }
+    }
+
+    /**
+     * @return the value of the maximum flow from the source to the sink.
+     */
+    public int getMaxFlow() {
+        return mMaxFlow;
+    }
+
+    /**
+     * @return the nodes which share the same partition (as defined by the min-cut edges)
+     * as the sink node.
+     */
+    public Set<V> getNodesInSinkPartition() {
+        return mSinkPartitionNodes;
+    }
+
+    /**
+     * @return the nodes which share the same partition (as defined by the min-cut edges)
+     * as the source node.
+     */
+    public Set<V> getNodesInSourcePartition() {
+        return mSourcePartitionNodes;
+    }
+
+    /**
+     * @return the edges in the minimum cut.
+     */
+    public Set<E> getMinCutEdges() {
+        return mMinCutEdges;
+    }
+
+    /**
+     * @return the graph for which the maximum flow is calculated.
+     */
+    public DirectedGraph<V,E> getFlowGraph() {
+        return mFlowGraph;
+    }
+
+    @Override
+    protected void initializeIterations() {
+        parentCapacityMap.put(source, Integer.MAX_VALUE);
+        parentMap.put(source, source);
+
+        List<E> edgeList = new ArrayList<E>(mFlowGraph.getEdges());
+
+        for (int eIdx=0;eIdx< edgeList.size();eIdx++) {
+            E edge = edgeList.get(eIdx);
+            Number capacity = edgeCapacityTransformer.apply(edge);
+
+            if (capacity == null) {
+                throw new IllegalArgumentException("Edge capacities must be provided in Function passed to constructor");
+            }
+            residualCapacityMap.put(edge, capacity);
+
+            V source = mFlowGraph.getSource(edge);
+            V destination = mFlowGraph.getDest(edge);
+
+            if(mFlowGraph.isPredecessor(source, destination) == false) {
+            	E backEdge = edgeFactory.get();
+            	mFlowGraph.addEdge(backEdge, destination, source, EdgeType.DIRECTED);
+                residualCapacityMap.put(backEdge, 0);
+            }
+        }
+    }
+    
+    @Override
+    protected void finalizeIterations() {
+
+        for (E currentEdge : mFlowGraph.getEdges()) {
+            Number capacity = edgeCapacityTransformer.apply(currentEdge);
+            
+            Number residualCapacity = residualCapacityMap.get(currentEdge);
+            if (capacity != null) {
+                Integer flowValue = new Integer(capacity.intValue()-residualCapacity.intValue());
+                this.edgeFlowMap.put(currentEdge, flowValue);
+            }
+        }
+
+        Set<E> backEdges = new HashSet<E>();
+        for (E currentEdge: mFlowGraph.getEdges()) {
+        	
+            if (edgeCapacityTransformer.apply(currentEdge) == null) {
+                backEdges.add(currentEdge);
+            } else {
+                residualCapacityMap.remove(currentEdge);
+            }
+        }
+        for(E e : backEdges) {
+        	mFlowGraph.removeEdge(e);
+        }
+    }
+
+    private void updateResidualCapacities() {
+
+        Number augmentingPathCapacity = parentCapacityMap.get(target);
+        mMaxFlow += augmentingPathCapacity.intValue();
+        V currentVertex = target;
+        V parentVertex = null;
+        while ((parentVertex = parentMap.get(currentVertex)) != currentVertex) {
+            E currentEdge = mFlowGraph.findEdge(parentVertex, currentVertex);
+
+            Number residualCapacity = residualCapacityMap.get(currentEdge);
+
+            residualCapacity = residualCapacity.intValue() - augmentingPathCapacity.intValue();
+            residualCapacityMap.put(currentEdge, residualCapacity);
+
+            E backEdge = mFlowGraph.findEdge(currentVertex, parentVertex);
+            residualCapacity = residualCapacityMap.get(backEdge);
+            residualCapacity = residualCapacity.intValue() + augmentingPathCapacity.intValue();
+            residualCapacityMap.put(backEdge, residualCapacity);
+            currentVertex = parentVertex;
+        }
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/flows/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/flows/package.html
new file mode 100644
index 0000000..502ae8d
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/flows/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Methods for calculating properties relating to network flows (such as max flow/min cut).
+
+</body>
+</html>
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/EvolvingGraphGenerator.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/EvolvingGraphGenerator.java
new file mode 100644
index 0000000..eddcf96
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/EvolvingGraphGenerator.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.generators;
+
+
+
+/**
+ * An interface for algorithms that generate graphs that evolve iteratively.
+ * @author Scott White
+ */
+public interface EvolvingGraphGenerator<V, E> extends GraphGenerator<V,E> {
+
+    /**
+     * Instructs the algorithm to evolve the graph N steps.
+     * @param numSteps number of steps to iterate from the current state
+     */
+    void evolveGraph(int numSteps);
+
+    /**
+     * Retrieves the total number of steps elapsed.
+     * @return number of elapsed steps
+     */
+    int numIterations();
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/GraphGenerator.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/GraphGenerator.java
new file mode 100644
index 0000000..6f1529f
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/GraphGenerator.java
@@ -0,0 +1,21 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.generators;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * An interface for algorithms that generate graphs.
+ * @author Scott White
+ */
+public interface GraphGenerator<V, E> extends Supplier<Graph<V,E>>{ }
+
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/Lattice2DGenerator.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/Lattice2DGenerator.java
new file mode 100644
index 0000000..cad79cd
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/Lattice2DGenerator.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2009, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.algorithms.generators;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+/**
+ * Simple generator of an m x n lattice where each vertex
+ * is incident with each of its neighbors (to the left, right, up, and down).
+ * May be toroidal, in which case the vertices on the edges are connected to
+ * their counterparts on the opposite edges as well.
+ * 
+ * <p>If the graph Supplier supplied has a default edge type of {@code EdgeType.DIRECTED},
+ * then edges will be created in both directions between adjacent vertices.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class Lattice2DGenerator<V,E> implements GraphGenerator<V,E>
+{
+    protected int row_count;
+    protected int col_count;
+    protected boolean is_toroidal;
+    protected boolean is_directed;
+    protected Supplier<? extends Graph<V, E>> graph_factory;
+    protected Supplier<V> vertex_factory;
+    protected Supplier<E> edge_factory;
+    private List<V> v_array;
+
+    /**
+     * Constructs a generator of square lattices of size {@code latticeSize} 
+     * with the specified parameters.
+     * 
+     * @param graph_factory used to create the {@code Graph} for the lattice
+     * @param vertex_factory used to create the lattice vertices
+     * @param edge_factory used to create the lattice edges
+     * @param latticeSize the number of rows and columns of the lattice
+     * @param isToroidal if true, the created lattice wraps from top to bottom and left to right
+     */
+    public Lattice2DGenerator(Supplier<? extends Graph<V,E>> graph_factory, Supplier<V> vertex_factory, 
+            Supplier<E> edge_factory, int latticeSize, boolean isToroidal)
+    {
+        this(graph_factory, vertex_factory, edge_factory, latticeSize, latticeSize, isToroidal);
+    }
+
+    /**
+     * Creates a generator of {@code row_count} x {@code col_count} lattices 
+     * with the specified parameters.
+     * 
+     * @param graph_factory used to create the {@code Graph} for the lattice
+     * @param vertex_factory used to create the lattice vertices
+     * @param edge_factory used to create the lattice edges
+     * @param row_count the number of rows in the lattice
+     * @param col_count the number of columns in the lattice
+     * @param isToroidal if true, the created lattice wraps from top to bottom and left to right
+     */
+    public Lattice2DGenerator(Supplier<? extends Graph<V,E>> graph_factory, Supplier<V> vertex_factory, 
+            Supplier<E> edge_factory, int row_count, int col_count, boolean isToroidal)
+    {
+        if (row_count < 2 || col_count < 2)
+        {
+            throw new IllegalArgumentException("Row and column counts must each be at least 2.");
+        }
+
+        this.row_count = row_count;
+        this.col_count = col_count;
+        this.is_toroidal = isToroidal;
+        this.graph_factory = graph_factory;
+        this.vertex_factory = vertex_factory;
+        this.edge_factory = edge_factory;
+        this.is_directed = (graph_factory.get().getDefaultEdgeType() == EdgeType.DIRECTED);
+    }
+    
+    /**
+     * Generates a graph based on the constructor-specified settings.
+     * 
+     * @return the generated graph
+     */
+    public Graph<V,E> get()
+    {
+        int vertex_count = row_count * col_count;
+        Graph<V,E> graph = graph_factory.get();
+        v_array = new ArrayList<V>(vertex_count);
+        for (int i = 0; i < vertex_count; i++)
+        {
+            V v = vertex_factory.get();
+            graph.addVertex(v);
+            v_array.add(i, v);
+        }
+
+        int start = is_toroidal ? 0 : 1;
+        int end_row = is_toroidal ? row_count : row_count - 1;
+        int end_col = is_toroidal ? col_count : col_count - 1;
+        
+        // fill in edges
+        // down
+        for (int i = 0; i < end_row; i++)
+            for (int j = 0; j < col_count; j++)
+                graph.addEdge(edge_factory.get(), getVertex(i,j), getVertex(i+1, j));
+        // right
+        for (int i = 0; i < row_count; i++)
+            for (int j = 0; j < end_col; j++)
+                graph.addEdge(edge_factory.get(), getVertex(i,j), getVertex(i, j+1));
+
+        // if the graph is directed, fill in the edges going the other direction...
+        if (graph.getDefaultEdgeType() == EdgeType.DIRECTED)
+        {
+            // up
+            for (int i = start; i < row_count; i++)
+                for (int j = 0; j < col_count; j++)
+                    graph.addEdge(edge_factory.get(), getVertex(i,j), getVertex(i-1, j));
+            // left
+            for (int i = 0; i < row_count; i++)
+                for (int j = start; j < col_count; j++)
+                    graph.addEdge(edge_factory.get(), getVertex(i,j), getVertex(i, j-1));
+        }
+        
+        return graph;
+    }
+
+    /**
+     * Returns the number of edges found in a lattice of this generator's specifications.
+     * (This is useful for subclasses that may modify the generated graphs to add more edges.)
+     * 
+     * @return the number of edges that this generator will generate
+     */
+    public int getGridEdgeCount()
+    {
+        int boundary_adjustment = (is_toroidal ? 0 : 1);
+        int vertical_edge_count = col_count * (row_count - boundary_adjustment);
+        int horizontal_edge_count = row_count * (col_count - boundary_adjustment);
+        
+        return (vertical_edge_count + horizontal_edge_count) * (is_directed ? 2 : 1);
+    }
+    
+    protected int getIndex(int i, int j)
+    {
+        return ((mod(i, row_count)) * col_count) + (mod(j, col_count));
+    }
+
+    protected int mod(int i, int modulus) 
+    {
+        int i_mod = i % modulus;
+        return i_mod >= 0 ? i_mod : i_mod + modulus;
+    }
+    
+    /**
+     * @param i row index into the lattice
+     * @param j column index into the lattice
+     * @return the vertex at position ({@code i mod row_count, j mod col_count})
+     */
+    protected V getVertex(int i, int j)
+    {
+        return v_array.get(getIndex(i, j));
+    }
+    
+    /**
+     * @param i row index into the lattice
+     * @return the {@code i}th vertex (counting row-wise)
+     */
+    protected V getVertex(int i)
+    {
+        return v_array.get(i);
+    }
+    
+    /**
+     * @param i index of the vertex whose row we want
+     * @return the row in which the vertex with index {@code i} is found
+     */
+    protected int getRow(int i)
+    {
+        return i / col_count;
+    }
+    
+    /**
+     * @param i index of the vertex whose column we want
+     * @return the column in which the vertex with index {@code i} is found
+     */
+    protected int getCol(int i)
+    {
+        return i % col_count;
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/package.html
new file mode 100644
index 0000000..261f4cf
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Methods for generating new (often random) graphs with various properties.
+
+</body>
+</html>
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/BarabasiAlbertGenerator.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/BarabasiAlbertGenerator.java
new file mode 100644
index 0000000..a834a2c
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/BarabasiAlbertGenerator.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.generators.random;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.generators.EvolvingGraphGenerator;
+import edu.uci.ics.jung.algorithms.util.WeightedChoice;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.MultiGraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * <p>
+ * Simple evolving scale-free random graph generator. At each time step, a new
+ * vertex is created and is connected to existing vertices according to the
+ * principle of "preferential attachment", whereby vertices with higher degree
+ * have a higher probability of being selected for attachment.
+ * 
+ * <p>
+ * At a given timestep, the probability <code>p</code> of creating an edge
+ * between an existing vertex <code>v</code> and the newly added vertex is
+ * 
+ * <pre>
+ * p = (degree(v) + 1) / (|E| + |V|);
+ * </pre>
+ * 
+ * <p>
+ * where <code>|E|</code> and <code>|V|</code> are, respectively, the number of
+ * edges and vertices currently in the network (counting neither the new vertex
+ * nor the other edges that are being attached to it).
+ * 
+ * <p>
+ * Note that the formula specified in the original paper (cited below) was
+ * 
+ * <pre>
+ * p = degree(v) / |E|
+ * </pre>
+ * 
+ * 
+ * <p>
+ * However, this would have meant that the probability of attachment for any
+ * existing isolated vertex would be 0. This version uses Lagrangian smoothing
+ * to give each existing vertex a positive attachment probability.
+ * 
+ * <p>
+ * The graph created may be either directed or undirected (controlled by a
+ * constructor parameter); the default is undirected. If the graph is specified
+ * to be directed, then the edges added will be directed from the newly added
+ * vertex u to the existing vertex v, with probability proportional to the
+ * indegree of v (number of edges directed towards v). If the graph is specified
+ * to be undirected, then the (undirected) edges added will connect u to v, with
+ * probability proportional to the degree of v.
+ * 
+ * <p>
+ * The <code>parallel</code> constructor parameter specifies whether parallel
+ * edges may be created.
+ * 
+ * @see "A.-L. Barabasi and R. Albert, Emergence of scaling in random networks, Science 286, 1999."
+ * @author Scott White
+ * @author Joshua O'Madadhain
+ * @author Tom Nelson - adapted to jung2
+ * @author James Marchant
+ */
+public class BarabasiAlbertGenerator<V, E> implements EvolvingGraphGenerator<V, E> {
+	private Graph<V, E> mGraph = null;
+	private int mNumEdgesToAttachPerStep;
+	private int mElapsedTimeSteps;
+	private Random mRandom;
+	protected List<V> vertex_index;
+	protected int init_vertices;
+	protected Map<V, Integer> index_vertex;
+	protected Supplier<Graph<V, E>> graphFactory;
+	protected Supplier<V> vertexFactory;
+	protected Supplier<E> edgeFactory;
+
+	/**
+	 * Constructs a new instance of the generator.
+	 * 
+	 * @param graphFactory
+	 *            factory for graphs of the appropriate type
+	 * @param vertexFactory
+	 *            factory for vertices of the appropriate type
+	 * @param edgeFactory
+	 *            factory for edges of the appropriate type
+	 * @param init_vertices
+	 *            number of unconnected 'seed' vertices that the graph should
+	 *            start with
+	 * @param numEdgesToAttach
+	 *            the number of edges that should be attached from the new
+	 *            vertex to pre-existing vertices at each time step
+	 * @param seed
+	 *            random number seed
+	 * @param seedVertices
+	 *            storage for the seed vertices that this graph creates
+	 */
+	// TODO: seedVertices is a bizarre way of exposing that information,
+	// refactor
+	public BarabasiAlbertGenerator(Supplier<Graph<V, E>> graphFactory, Supplier<V> vertexFactory,
+			Supplier<E> edgeFactory, int init_vertices, int numEdgesToAttach, int seed, Set<V> seedVertices) {
+		Preconditions.checkArgument(init_vertices > 0,
+				"Number of initial unconnected 'seed' vertices must be positive");
+		Preconditions.checkArgument(numEdgesToAttach > 0,
+				"Number of edges to attach at each time step must be positive");
+		Preconditions.checkArgument(numEdgesToAttach <= init_vertices,
+				"Number of edges to attach at each time step must less than or equal to the number of initial vertices");
+
+		mNumEdgesToAttachPerStep = numEdgesToAttach;
+		mRandom = new Random(seed);
+		this.graphFactory = graphFactory;
+		this.vertexFactory = vertexFactory;
+		this.edgeFactory = edgeFactory;
+		this.init_vertices = init_vertices;
+		initialize(seedVertices);
+	}
+
+	/**
+	 * Constructs a new instance of the generator, whose output will be an
+	 * undirected graph, and which will use the current time as a seed for the
+	 * random number generation.
+	 * 
+	 * @param graphFactory
+	 *            factory for graphs of the appropriate type
+	 * @param vertexFactory
+	 *            factory for vertices of the appropriate type
+	 * @param edgeFactory
+	 *            factory for edges of the appropriate type
+	 * @param init_vertices
+	 *            number of vertices that the graph should start with
+	 * @param numEdgesToAttach
+	 *            the number of edges that should be attached from the new
+	 *            vertex to pre-existing vertices at each time step
+	 * @param seedVertices
+	 *            storage for the seed vertices that this graph creates
+	 */
+	public BarabasiAlbertGenerator(Supplier<Graph<V, E>> graphFactory, Supplier<V> vertexFactory,
+			Supplier<E> edgeFactory, int init_vertices, int numEdgesToAttach, Set<V> seedVertices) {
+		this(graphFactory, vertexFactory, edgeFactory, init_vertices, numEdgesToAttach,
+				(int) System.currentTimeMillis(), seedVertices);
+	}
+
+	private void initialize(Set<V> seedVertices) {
+		mGraph = graphFactory.get();
+
+		vertex_index = new ArrayList<V>(2 * init_vertices);
+		index_vertex = new HashMap<V, Integer>(2 * init_vertices);
+		for (int i = 0; i < init_vertices; i++) {
+			V v = vertexFactory.get();
+			mGraph.addVertex(v);
+			vertex_index.add(v);
+			index_vertex.put(v, i);
+			seedVertices.add(v);
+		}
+
+		mElapsedTimeSteps = 0;
+	}
+
+	public void evolveGraph(int numTimeSteps) {
+
+		for (int i = 0; i < numTimeSteps; i++) {
+			evolveGraph();
+			mElapsedTimeSteps++;
+		}
+	}
+
+	private void evolveGraph() {
+		Collection<V> preexistingNodes = mGraph.getVertices();
+		V newVertex = vertexFactory.get();
+
+		mGraph.addVertex(newVertex);
+
+		// generate and store the new edges; don't add them to the graph
+		// yet because we don't want to bias the degree calculations
+		// (all new edges in a timestep should be added in parallel)
+		Set<Pair<V>> added_pairs = createRandomEdges(preexistingNodes, newVertex, mNumEdgesToAttachPerStep);
+
+		for (Pair<V> pair : added_pairs) {
+			V v1 = pair.getFirst();
+			V v2 = pair.getSecond();
+			if (mGraph.getDefaultEdgeType() != EdgeType.UNDIRECTED || !mGraph.isNeighbor(v1, v2))
+				mGraph.addEdge(edgeFactory.get(), pair);
+		}
+		// now that we're done attaching edges to this new vertex,
+		// add it to the index
+		vertex_index.add(newVertex);
+		index_vertex.put(newVertex, new Integer(vertex_index.size() - 1));
+	}
+
+	private Set<Pair<V>> createRandomEdges(Collection<V> preexistingNodes, V newVertex, int numEdges) {
+		Set<Pair<V>> added_pairs = new HashSet<Pair<V>>(numEdges * 3);
+
+		/* Generate the probability distribution */
+		Map<V, Double> item_weights = new HashMap<V, Double>();
+		for (V v : preexistingNodes) {
+			/*
+			 * as preexistingNodes is a view onto the vertex set, it will
+			 * contain the new node. In the construction of Barabasi-Albert,
+			 * there should be no self-loops.
+			 */
+			if (v == newVertex)
+				continue;
+
+			double degree;
+			double denominator;
+
+			/*
+			 * Attachment probability is dependent on whether the graph is
+			 * directed or undirected.
+			 * 
+			 * Subtract 1 from numVertices because we don't want to count
+			 * newVertex (which has already been added to the graph, but not to
+			 * vertex_index).
+			 */
+			if (mGraph.getDefaultEdgeType() == EdgeType.UNDIRECTED) {
+				degree = mGraph.degree(v);
+				denominator = (2 * mGraph.getEdgeCount()) + mGraph.getVertexCount() - 1;
+			} else {
+				degree = mGraph.inDegree(v);
+				denominator = mGraph.getEdgeCount() + mGraph.getVertexCount() - 1;
+			}
+
+			double prob = (degree + 1) / denominator;
+			item_weights.put(v, prob);
+		}
+		WeightedChoice<V> nodeProbabilities = new WeightedChoice<V>(item_weights, mRandom);
+
+		for (int i = 0; i < numEdges; i++) {
+			createRandomEdge(preexistingNodes, newVertex, added_pairs, nodeProbabilities);
+		}
+
+		return added_pairs;
+	}
+
+	private void createRandomEdge(Collection<V> preexistingNodes, V newVertex, Set<Pair<V>> added_pairs,
+			WeightedChoice<V> weightedProbabilities) {
+		V attach_point;
+		boolean created_edge = false;
+		Pair<V> endpoints;
+
+		do {
+			attach_point = weightedProbabilities.nextItem();
+
+			endpoints = new Pair<V>(newVertex, attach_point);
+
+			/*
+			 * If parallel edges are not allowed, skip attach_point if
+			 * <newVertex, attach_point> already exists; note that because of
+			 * the way the new node's edges are added, we only need to check the
+			 * list of candidate edges for duplicates.
+			 */
+			if (!(mGraph instanceof MultiGraph)) {
+				if (added_pairs.contains(endpoints))
+					continue;
+				if (mGraph.getDefaultEdgeType() == EdgeType.UNDIRECTED
+						&& added_pairs.contains(new Pair<V>(attach_point, newVertex)))
+					continue;
+			}
+			created_edge = true;
+		} while (!created_edge);
+
+		added_pairs.add(endpoints);
+
+		if (mGraph.getDefaultEdgeType() == EdgeType.UNDIRECTED) {
+			added_pairs.add(new Pair<V>(attach_point, newVertex));
+		}
+	}
+
+	public int numIterations() {
+		return mElapsedTimeSteps;
+	}
+
+	public Graph<V, E> get() {
+		return mGraph;
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/EppsteinPowerLawGenerator.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/EppsteinPowerLawGenerator.java
new file mode 100644
index 0000000..d02cdba
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/EppsteinPowerLawGenerator.java
@@ -0,0 +1,128 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.generators.random;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.generators.GraphGenerator;
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * Graph generator that generates undirected graphs with power-law degree distributions.
+ * @author Scott White
+ * @see "A Steady State Model for Graph Power Law by David Eppstein and Joseph Wang"
+ */
+public class EppsteinPowerLawGenerator<V,E> implements GraphGenerator<V,E> {
+    private int mNumVertices;
+    private int mNumEdges;
+    private int mNumIterations;
+    private double mMaxDegree;
+    private Random mRandom;
+    private Supplier<Graph<V,E>> graphFactory;
+    private Supplier<V> vertexFactory;
+    private Supplier<E> edgeFactory;
+
+    /**
+     * Creates an instance with the specified factories and specifications.
+     * @param graphFactory the Supplier to use to generate the graph
+     * @param vertexFactory the Supplier to use to create vertices
+     * @param edgeFactory the Supplier to use to create edges
+     * @param numVertices the number of vertices for the generated graph
+     * @param numEdges the number of edges the generated graph will have, should be Theta(numVertices)
+     * @param r the number of iterations to use; the larger the value the better the graph's degree
+     * distribution will approximate a power-law
+     */
+    public EppsteinPowerLawGenerator(Supplier<Graph<V,E>> graphFactory,
+    		Supplier<V> vertexFactory, Supplier<E> edgeFactory, 
+    		int numVertices, int numEdges, int r) {
+    	this.graphFactory = graphFactory;
+    	this.vertexFactory = vertexFactory;
+    	this.edgeFactory = edgeFactory;
+        mNumVertices = numVertices;
+        mNumEdges = numEdges;
+        mNumIterations = r;
+        mRandom = new Random();
+    }
+
+    protected Graph<V,E> initializeGraph() {
+        Graph<V,E> graph = null;
+        graph = graphFactory.get();
+        for(int i=0; i<mNumVertices; i++) {
+        	graph.addVertex(vertexFactory.get());
+        }
+        List<V> vertices = new ArrayList<V>(graph.getVertices());
+        while (graph.getEdgeCount() < mNumEdges) {
+            V u = vertices.get((int) (mRandom.nextDouble() * mNumVertices));
+            V v = vertices.get((int) (mRandom.nextDouble() * mNumVertices));
+            if (!graph.isSuccessor(v,u)) {
+            	graph.addEdge(edgeFactory.get(), u, v);
+            }
+        }
+
+        double maxDegree = 0;
+        for (V v : graph.getVertices()) {
+            maxDegree = Math.max(graph.degree(v),maxDegree);
+        }
+        mMaxDegree = maxDegree; //(maxDegree+1)*(maxDegree)/2;
+
+        return graph;
+    }
+
+    /**
+     * Generates a graph whose degree distribution approximates a power-law.
+     * @return the generated graph
+     */
+    public Graph<V,E> get() {
+        Graph<V,E> graph = initializeGraph();
+
+        List<V> vertices = new ArrayList<V>(graph.getVertices());
+        for (int rIdx = 0; rIdx < mNumIterations; rIdx++) {
+
+            V v = null;
+            int degree = 0;
+            do {
+                v = vertices.get((int) (mRandom.nextDouble() * mNumVertices));
+                degree = graph.degree(v);
+
+            } while (degree == 0);
+
+            List<E> edges = new ArrayList<E>(graph.getIncidentEdges(v));
+            E randomExistingEdge = edges.get((int) (mRandom.nextDouble()*degree));
+
+            // FIXME: look at email thread on a more efficient RNG for arbitrary distributions
+            
+            V x = vertices.get((int) (mRandom.nextDouble() * mNumVertices));
+            V y = null;
+            do {
+                y = vertices.get((int) (mRandom.nextDouble() * mNumVertices));
+
+            } while (mRandom.nextDouble() > ((graph.degree(y)+1)/mMaxDegree));
+
+            if (!graph.isSuccessor(y,x) && x != y) {
+                graph.removeEdge(randomExistingEdge);
+                graph.addEdge(edgeFactory.get(), x, y);
+            }
+        }
+
+        return graph;
+    }
+
+    /**
+     * Sets the seed for the random number generator.
+     * @param seed input to the random number generator.
+     */
+    public void setSeed(long seed) {
+        mRandom.setSeed(seed);
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/ErdosRenyiGenerator.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/ErdosRenyiGenerator.java
new file mode 100644
index 0000000..68f146f
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/ErdosRenyiGenerator.java
@@ -0,0 +1,105 @@
+/*
+* Copyright (c) 2003, The JUNG Authors
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.generators.random;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.generators.GraphGenerator;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+
+/**
+ * Generates a random graph using the Erdos-Renyi binomial model
+ * (each pair of vertices is connected with probability p).
+ * 
+ *  @author William Giordano, Scott White, Joshua O'Madadhain
+ */
+public class ErdosRenyiGenerator<V,E> implements GraphGenerator<V,E> {
+    private int mNumVertices;
+    private double mEdgeConnectionProbability;
+    private Random mRandom;
+    Supplier<UndirectedGraph<V,E>> graphFactory;
+    Supplier<V> vertexFactory;
+    Supplier<E> edgeFactory;
+
+    /**
+     *
+     * @param graphFactory factory for graphs of the appropriate type
+     * @param vertexFactory factory for vertices of the appropriate type
+     * @param edgeFactory factory for edges of the appropriate type
+     * @param numVertices number of vertices graph should have
+     * @param p Connection's probability between 2 vertices
+     */
+	public ErdosRenyiGenerator(Supplier<UndirectedGraph<V,E>> graphFactory,
+			Supplier<V> vertexFactory, Supplier<E> edgeFactory,
+			int numVertices,double p)
+    {
+        if (numVertices <= 0) {
+            throw new IllegalArgumentException("A positive # of vertices must be specified.");
+        }
+        mNumVertices = numVertices;
+        if (p < 0 || p > 1) {
+            throw new IllegalArgumentException("p must be between 0 and 1.");
+        }
+        this.graphFactory = graphFactory;
+        this.vertexFactory = vertexFactory;
+        this.edgeFactory = edgeFactory;
+        mEdgeConnectionProbability = p;
+        mRandom = new Random();
+	}
+
+    /**
+     * Returns a graph in which each pair of vertices is connected by 
+     * an undirected edge with the probability specified by the constructor.
+     */
+	public Graph<V,E> get() {
+        UndirectedGraph<V,E> g = graphFactory.get();
+        for(int i=0; i<mNumVertices; i++) {
+        	g.addVertex(vertexFactory.get());
+        }
+        List<V> list = new ArrayList<V>(g.getVertices());
+
+		for (int i = 0; i < mNumVertices-1; i++) {
+            V v_i = list.get(i);
+			for (int j = i+1; j < mNumVertices; j++) {
+                V v_j = list.get(j);
+				if (mRandom.nextDouble() < mEdgeConnectionProbability) {
+					g.addEdge(edgeFactory.get(), v_i, v_j);
+				}
+			}
+		}
+        return g;
+    }
+
+    /**
+     * Sets the seed of the internal random number generator to {@code seed}.
+     * Enables consistent behavior.
+     * 
+     * @param seed the seed to use for the internal random number generator
+     */
+    public void setSeed(long seed) {
+        mRandom.setSeed(seed);
+    }
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/KleinbergSmallWorldGenerator.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/KleinbergSmallWorldGenerator.java
new file mode 100644
index 0000000..98b0d49
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/KleinbergSmallWorldGenerator.java
@@ -0,0 +1,192 @@
+
+package edu.uci.ics.jung.algorithms.generators.random;
+
+/*
+* Copyright (c) 2009, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.generators.Lattice2DGenerator;
+import edu.uci.ics.jung.algorithms.util.WeightedChoice;
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * Graph generator that produces a random graph with small world properties. 
+ * The underlying model is an mxn (optionally toroidal) lattice. Each node u 
+ * has four local connections, one to each of its neighbors, and
+ * in addition 1+ long range connections to some node v where v is chosen randomly according to
+ * probability proportional to d^-alpha where d is the lattice distance between u and v and alpha
+ * is the clustering exponent.
+ * 
+ * @see "Navigation in a small world J. Kleinberg, Nature 406(2000), 845."
+ * @author Joshua O'Madadhain
+ */
+public class KleinbergSmallWorldGenerator<V, E> extends Lattice2DGenerator<V, E> {
+    private double clustering_exponent;
+    private Random random;
+    private int num_connections = 1;
+    
+    /**
+     * Creates an instance with the specified parameters, whose underlying lattice is (a) of size
+     * {@code latticeSize} x {@code latticeSize}, and (b) toroidal.
+     * @param graphFactory factory for graphs of the appropriate type
+     * @param vertexFactory factory for vertices of the appropriate type
+     * @param edgeFactory factory for edges of the appropriate type
+     * @param latticeSize the number of rows and columns of the underlying lattice
+     * @param clusteringExponent the clustering exponent
+     */
+    public KleinbergSmallWorldGenerator(Supplier<? extends Graph<V,E>> graphFactory,
+    		Supplier<V> vertexFactory, Supplier<E> edgeFactory,
+    		int latticeSize, double clusteringExponent) 
+    {
+        this(graphFactory, vertexFactory, edgeFactory, latticeSize, latticeSize, clusteringExponent);
+    }
+
+    /**
+     * Creates an instance with the specified parameters, whose underlying lattice is toroidal.
+     * @param graphFactory factory for graphs of the appropriate type
+     * @param vertexFactory factory for vertices of the appropriate type
+     * @param edgeFactory factory for edges of the appropriate type
+     * @param row_count number of rows of the underlying lattice
+     * @param col_count number of columns of the underlying lattice
+     * @param clusteringExponent the clustering exponent
+     */
+    public KleinbergSmallWorldGenerator(Supplier<? extends Graph<V,E>> graphFactory,
+    		Supplier<V> vertexFactory, Supplier<E> edgeFactory,
+            int row_count, int col_count, double clusteringExponent) 
+    {
+        super(graphFactory, vertexFactory, edgeFactory, row_count, col_count, true);
+        clustering_exponent = clusteringExponent;
+        initialize();
+    }
+
+    /**
+     * Creates an instance with the specified parameters.
+     * @param graphFactory factory for graphs of the appropriate type
+     * @param vertexFactory factory for vertices of the appropriate type
+     * @param edgeFactory factory for edges of the appropriate type
+     * @param row_count number of rows of the underlying lattice
+     * @param col_count number of columns of the underlying lattice
+     * @param clusteringExponent the clustering exponent
+     * @param isToroidal whether the underlying lattice is toroidal
+     */
+    public KleinbergSmallWorldGenerator(Supplier<? extends Graph<V,E>> graphFactory,
+    		Supplier<V> vertexFactory, Supplier<E> edgeFactory, 
+    		int row_count, int col_count, double clusteringExponent, boolean isToroidal) 
+    {
+        super(graphFactory, vertexFactory, edgeFactory, row_count, col_count, isToroidal);
+        clustering_exponent = clusteringExponent;
+        initialize();
+    }
+
+    private void initialize()
+    {
+        this.random = new Random();
+    }
+    
+    /**
+     * Sets the {@code Random} instance used by this instance.  Useful for 
+     * unit testing.
+     * @param random the {@code Random} instance for this class to use
+     */
+    public void setRandom(Random random)
+    {
+        this.random = random;
+    }
+    
+    /**
+     * Sets the seed of the internal random number generator.  May be used to provide repeatable
+     * experiments.
+     * @param seed the random seed that this class's random number generator is to use
+     */
+    public void setRandomSeed(long seed) 
+    {
+        random.setSeed(seed);
+    }
+
+    /**
+     * Sets the number of new 'small-world' connections (outgoing edges) to be added to each vertex.
+     * @param num_connections the number of outgoing small-world edges to add to each vertex
+     */
+    public void setConnectionCount(int num_connections)
+    {
+        if (num_connections <= 0)
+        {
+            throw new IllegalArgumentException("Number of new connections per vertex must be >= 1");
+        }
+        this.num_connections = num_connections;
+    }
+
+    /**
+     * @return the number of new 'small-world' connections that will originate at each vertex
+     */
+    public int getConnectionCount()
+    {
+        return this.num_connections;
+    }
+    
+    /**
+     * Generates a random small world network according to the parameters given
+     * @return a random small world graph
+     */
+    @Override
+    public Graph<V,E> get() 
+    {
+        Graph<V, E> graph = super.get();
+        
+        // TODO: For toroidal graphs, we can make this more clever by pre-creating the WeightedChoice object
+        // and using the output as an offset to the current vertex location.
+        WeightedChoice<V> weighted_choice;
+        
+        // Add long range connections
+        for (int i = 0; i < graph.getVertexCount(); i++)
+        {
+            V source = getVertex(i);
+            int row = getRow(i);
+            int col = getCol(i);
+            int row_offset = row < row_count/2 ? -row_count : row_count;
+            int col_offset = col < col_count/2 ? -col_count : col_count;
+
+            Map<V, Float> vertex_weights = new HashMap<V, Float>();
+            for (int j = 0; j < row_count; j++)
+            {
+                for (int k = 0; k < col_count; k++)
+                {
+                    if (j == row && k == col)
+                        continue;
+                    int v_dist = Math.abs(j - row);
+                    int h_dist = Math.abs(k - col);
+                    if (is_toroidal)
+                    {
+                        v_dist = Math.min(v_dist, Math.abs(j - row+row_offset));
+                        h_dist = Math.min(h_dist, Math.abs(k - col+col_offset));
+                    }
+                    int distance = v_dist + h_dist;
+                    if (distance < 2)
+                        continue;
+                    else
+                        vertex_weights.put(getVertex(j,k), (float)Math.pow(distance, -clustering_exponent));
+                }
+            }
+
+            for (int j = 0; j < this.num_connections; j++) {
+                weighted_choice = new WeightedChoice<V>(vertex_weights, random);
+                V target = weighted_choice.nextItem();
+                graph.addEdge(edge_factory.get(), source, target);
+            }
+        }
+
+        return graph;
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/MixedRandomGraphGenerator.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/MixedRandomGraphGenerator.java
new file mode 100644
index 0000000..6a932ca
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/MixedRandomGraphGenerator.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Jul 2, 2003
+ *  
+ */
+package edu.uci.ics.jung.algorithms.generators.random;
+
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+/**
+ * Generates a mixed-mode random graph (with random edge weights) based on the output of 
+ * <code>BarabasiAlbertGenerator</code>.
+ * Primarily intended for providing a heterogeneous sample graph for visualization testing, etc.
+ */
+public class MixedRandomGraphGenerator {
+
+    /**
+     * Returns a random mixed-mode graph.  Starts with a randomly generated 
+     * Barabasi-Albert (preferential attachment) generator 
+     * (4 initial vertices, 3 edges added at each step, and num_vertices - 4 evolution steps).
+     * Then takes the resultant graph, replaces random undirected edges with directed
+     * edges, and assigns random weights to each edge.
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+     * @param graphFactory factory for graphs of the appropriate type
+     * @param vertexFactory factory for vertices of the appropriate type
+     * @param edgeFactory factory for edges of the appropriate type
+     * @param edge_weights storage for the edge weights that this generator creates
+     * @param num_vertices number of vertices to generate
+     * @param seedVertices storage for the seed vertices that this generator creates
+     * @return the generated graph
+     */
+    public static <V,E> Graph<V,E> generateMixedRandomGraph(
+    		Supplier<Graph<V,E>> graphFactory,
+    		Supplier<V> vertexFactory,
+    		Supplier<E> edgeFactory,
+    		Map<E,Number> edge_weights, 
+            int num_vertices, Set<V> seedVertices)
+    {
+        int seed = (int)(Math.random() * 10000);
+        BarabasiAlbertGenerator<V,E> bag = 
+            new BarabasiAlbertGenerator<V,E>(graphFactory, vertexFactory, edgeFactory,
+            		4, 3, //false, parallel, 
+            		seed, seedVertices);
+        bag.evolveGraph(num_vertices - 4);
+        Graph<V, E> ug = bag.get();
+
+        Graph<V, E> g = graphFactory.get();
+        for(V v : ug.getVertices()) {
+        	g.addVertex(v);
+        }
+        
+        // randomly replace some of the edges by directed edges to 
+        // get a mixed-mode graph, add random weights
+        
+        for(E e : ug.getEdges()) {
+            V v1 = ug.getEndpoints(e).getFirst();
+            V v2 = ug.getEndpoints(e).getSecond();
+
+            E me = edgeFactory.get();
+            g.addEdge(me, v1, v2, Math.random() < .5 ? EdgeType.DIRECTED : EdgeType.UNDIRECTED);
+            edge_weights.put(me, Math.random());
+        }
+        
+        return g;
+    }
+    
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/package.html
new file mode 100644
index 0000000..331faaa
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/generators/random/package.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Methods for generating random graphs with various properties.  These include:
+<ul>
+<li><code>BarabasiAlbertGenerator</code>: scale-free graphs using the preferential attachment heuristic.
+<li><code>EppsteinPowerLawGenerator</code>: graphs whose degree distribution approximates a power law
+<li><code>ErdosRenyiGenerator</code>: graphs for which edges are created with a specified probability
+<li><code>MixedRandomGraphGenerator</code>: takes the output of <code>BarabasiAlbertGenerator</code> and
+perturbs it to generate a mixed-mode analog with both directed and undirected edges. 
+<li>
+
+
+</body>
+</html>
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/AbstractRanker.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/AbstractRanker.java
new file mode 100644
index 0000000..a9aa61c
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/AbstractRanker.java
@@ -0,0 +1,392 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.importance;
+
+import java.text.DecimalFormat;
+import java.text.Format;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import edu.uci.ics.jung.algorithms.util.IterativeProcess;
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * Abstract class for algorithms that rank nodes or edges by some "importance" metric. Provides a common set of
+ * services such as:
+ * <ul>
+ *  <li> storing rank scores</li>
+ *  <li> getters and setters for rank scores</li>
+ *  <li> computing default edge weights</li>
+ *  <li> normalizing default or user-provided edge transition weights </li>
+ *  <li> normalizing rank scores</li>
+ *  <li> automatic cleanup of decorations</li>
+ *  <li> creation of Ranking list</li>
+ * <li>print rankings in sorted order by rank</li>
+ * </ul>
+ * <p>
+ * By default, all rank scores are removed from the vertices (or edges) being ranked.
+ * @author Scott White
+ */
+public abstract class AbstractRanker<V,E> extends IterativeProcess {
+    private Graph<V,E> mGraph;
+    private List<Ranking<?>> mRankings;
+    private boolean mRemoveRankScoresOnFinalize;
+    private boolean mRankNodes;
+    private boolean mRankEdges;
+    private boolean mNormalizeRankings;
+    protected LoadingCache<Object, Map<V, Number>> vertexRankScores
+    	= CacheBuilder.newBuilder().build(new CacheLoader<Object, Map<V, Number>>() {
+	    	public Map<V, Number> load(Object o) {
+	    		return new HashMap<V, Number>();
+	    	}
+	});
+    protected LoadingCache<Object, Map<E, Number>> edgeRankScores
+    	= CacheBuilder.newBuilder().build(new CacheLoader<Object, Map<E, Number>>() {
+	    	public Map<E, Number> load(Object o) {
+	    		return new HashMap<E, Number>();
+	    	}
+    });
+    
+    private Map<E,Number> edgeWeights = new HashMap<E,Number>();
+
+    protected void initialize(Graph<V,E> graph, boolean isNodeRanker, 
+        boolean isEdgeRanker) {
+        if (!isNodeRanker && !isEdgeRanker)
+            throw new IllegalArgumentException("Must rank edges, vertices, or both");
+        mGraph = graph;
+        mRemoveRankScoresOnFinalize = true;
+        mNormalizeRankings = true;
+        mRankNodes = isNodeRanker;
+        mRankEdges = isEdgeRanker;
+    }
+    
+    /**
+	 * @return all rankScores
+	 */
+	public Map<Object,Map<V, Number>> getVertexRankScores() {
+		return vertexRankScores.asMap();
+	}
+
+	public Map<Object,Map<E, Number>> getEdgeRankScores() {
+		return edgeRankScores.asMap();
+	}
+
+    /**
+     * @param key the rank score key whose scores are to be retrieved
+	 * @return the rank scores for the specified key
+	 */
+	public Map<V, Number> getVertexRankScores(Object key) {
+		return vertexRankScores.getUnchecked(key);
+	}
+
+	public Map<E, Number> getEdgeRankScores(Object key) {
+		return edgeRankScores.getUnchecked(key);
+	}
+
+	protected Collection<V> getVertices() {
+        return mGraph.getVertices();
+    }
+
+	protected int getVertexCount() {
+        return mGraph.getVertexCount();
+    }
+
+    protected Graph<V,E> getGraph() {
+        return mGraph;
+    }
+
+    @Override
+    public void reset() {
+    }
+
+    /**
+     * @return <code>true</code> if this ranker ranks nodes, and 
+     * <code>false</code> otherwise.
+     */
+    public boolean isRankingNodes() {
+        return mRankNodes;
+    }
+
+    /**
+     * @return <code>true</code> if this ranker ranks edges, and 
+     * <code>false</code> otherwise.
+     */
+    public boolean isRankingEdges() {
+        return mRankEdges;
+    }
+    
+    /**
+     * Instructs the ranker whether or not it should remove the rank scores from the nodes (or edges) once the ranks
+     * have been computed.
+     * @param removeRankScoresOnFinalize <code>true</code> if the rank scores are to be removed, <code>false</code> otherwise
+     */
+    public void setRemoveRankScoresOnFinalize(boolean removeRankScoresOnFinalize) {
+        this.mRemoveRankScoresOnFinalize = removeRankScoresOnFinalize;
+    }
+
+    protected void onFinalize(Object e) {}
+    
+    /**
+     * The user datum key used to store the rank score.
+     * @return the key
+     */
+    abstract public Object getRankScoreKey();
+
+
+	@SuppressWarnings("unchecked")
+	@Override
+    protected void finalizeIterations() {
+        List<Ranking<?>> sortedRankings = new ArrayList<Ranking<?>>();
+
+        int id = 1;
+        if (mRankNodes) {
+            for (V currentVertex : getVertices()) {
+                Ranking<V> ranking = new Ranking<V>(id,getVertexRankScore(currentVertex),currentVertex);
+                sortedRankings.add(ranking);
+                if (mRemoveRankScoresOnFinalize) {
+                	this.vertexRankScores.getUnchecked(getRankScoreKey()).remove(currentVertex);
+                }
+                id++;
+                onFinalize(currentVertex);
+            }
+        }
+        if (mRankEdges) {
+            for (E currentEdge : mGraph.getEdges()) {
+
+                Ranking<E> ranking = new Ranking<E>(id,getEdgeRankScore(currentEdge),currentEdge);
+                sortedRankings.add(ranking);
+                if (mRemoveRankScoresOnFinalize) {
+                	this.edgeRankScores.getUnchecked(getRankScoreKey()).remove(currentEdge);
+                }
+                id++;
+                onFinalize(currentEdge);
+            }
+        }
+
+        mRankings = sortedRankings;
+        Collections.sort(mRankings);
+    }
+
+    /**
+     * Retrieves the list of ranking instances in descending sorted order by rank score
+     * If the algorithm is ranking edges, the instances will be of type <code>EdgeRanking</code>, otherwise
+     * if the algorithm is ranking nodes the instances will be of type <code>NodeRanking</code>
+     * @return  the list of rankings
+     */
+    public List<Ranking<?>> getRankings() {
+        return mRankings;
+    }
+
+    /**
+     * Return a list of the top k rank scores.
+     * @param topKRankings the value of k to use
+     * @return list of rank scores
+     */
+    public List<Double> getRankScores(int topKRankings) {
+        List<Double> scores = new ArrayList<Double>();
+        int count=1;
+        for (Ranking<?> currentRanking : getRankings()) {
+            if (count > topKRankings) {
+                return scores;
+            }
+            scores.add(currentRanking.rankScore);
+            count++;
+        }
+
+        return scores;
+    }
+
+    /**
+     * Given a node, returns the corresponding rank score. This is a default
+     * implementation of getRankScore which assumes the decorations are of type MutableDouble.
+     * This method only returns legal values if <code>setRemoveRankScoresOnFinalize(false)</code> was called
+     * prior to <code>evaluate()</code>.
+     * 
+     * @param v the node whose rank score is to be returned.
+     * @return  the rank score value
+     */
+    public double getVertexRankScore(V v) {
+        Number rankScore = vertexRankScores.getUnchecked(getRankScoreKey()).get(v);
+        if (rankScore != null) {
+            return rankScore.doubleValue();
+        } else {
+            throw new RuntimeException("setRemoveRankScoresOnFinalize(false) must be called before evaluate().");
+        }
+    }
+    
+    public double getVertexRankScore(V v, Object key) {
+    	return vertexRankScores.getUnchecked(key).get(v).doubleValue();
+    }
+
+    public double getEdgeRankScore(E e) {
+        Number rankScore = edgeRankScores.getUnchecked(getRankScoreKey()).get(e);
+        if (rankScore != null) {
+            return rankScore.doubleValue();
+        } else {
+            throw new RuntimeException("setRemoveRankScoresOnFinalize(false) must be called before evaluate().");
+        }
+    }
+    
+    public double getEdgeRankScore(E e, Object key) {
+    	return edgeRankScores.getUnchecked(key).get(e).doubleValue();
+    }
+
+    protected void setVertexRankScore(V v, double rankValue, Object key) {
+    	vertexRankScores.getUnchecked(key).put(v, rankValue);
+    }
+
+    protected void setEdgeRankScore(E e, double rankValue, Object key) {
+		edgeRankScores.getUnchecked(key).put(e, rankValue);
+    }
+
+    protected void setVertexRankScore(V v, double rankValue) {
+    	setVertexRankScore(v,rankValue, getRankScoreKey());
+    }
+
+    protected void setEdgeRankScore(E e, double rankValue) {
+    	setEdgeRankScore(e, rankValue, getRankScoreKey());
+    }
+
+    protected void removeVertexRankScore(V v, Object key) {
+    	vertexRankScores.getUnchecked(key).remove(v);
+    }
+
+    protected void removeEdgeRankScore(E e, Object key) {
+    	edgeRankScores.getUnchecked(key).remove(e);
+    }
+
+    protected void removeVertexRankScore(V v) {
+    	vertexRankScores.getUnchecked(getRankScoreKey()).remove(v);
+    }
+
+    protected void removeEdgeRankScore(E e) {
+    	edgeRankScores.getUnchecked(getRankScoreKey()).remove(e);
+    }
+
+    protected double getEdgeWeight(E e) {
+    	return edgeWeights.get(e).doubleValue();
+    }
+
+    protected void setEdgeWeight(E e, double weight) {
+    	edgeWeights.put(e, weight);
+    }
+    
+    public void setEdgeWeights(Map<E,Number> edgeWeights) {
+    	this.edgeWeights = edgeWeights;
+    }
+
+    /**
+	 * @return the edgeWeights
+	 */
+	public Map<E, Number> getEdgeWeights() {
+		return edgeWeights;
+	}
+
+	protected void assignDefaultEdgeTransitionWeights() {
+
+        for (V currentVertex : getVertices()) {
+
+            Collection<E> outgoingEdges = mGraph.getOutEdges(currentVertex);
+
+            double numOutEdges = outgoingEdges.size();
+            for (E currentEdge : outgoingEdges) {
+                setEdgeWeight(currentEdge,1.0/numOutEdges);
+            }
+        }
+    }
+
+    protected void normalizeEdgeTransitionWeights() {
+
+        for (V currentVertex : getVertices()) {
+
+        	Collection<E> outgoingEdges = mGraph.getOutEdges(currentVertex);
+
+            double totalEdgeWeight = 0;
+            for (E currentEdge : outgoingEdges) {
+                totalEdgeWeight += getEdgeWeight(currentEdge);
+            }
+
+            for (E currentEdge : outgoingEdges) {
+                setEdgeWeight(currentEdge,getEdgeWeight(currentEdge)/totalEdgeWeight);
+            }
+        }
+    }
+
+    protected void normalizeRankings() {
+        if (!mNormalizeRankings) {
+            return;
+        }
+        double totalWeight = 0;
+
+        for (V currentVertex : getVertices()) {
+            totalWeight += getVertexRankScore(currentVertex);
+        }
+
+        for (V currentVertex : getVertices()) {
+            setVertexRankScore(currentVertex,getVertexRankScore(currentVertex)/totalWeight);
+        }
+    }
+
+    /**
+     * Print the rankings to standard out in descending order of rank score
+     * @param verbose if <code>true</code>, include information about the actual rank order as well as
+     * the original position of the vertex before it was ranked
+     * @param printScore if <code>true</code>, include the actual value of the rank score
+     */
+    public void printRankings(boolean verbose,boolean printScore) {
+            double total = 0;
+            Format formatter = new DecimalFormat("#0.#######");
+            int rank = 1;
+
+            for (Ranking<?> currentRanking : getRankings()) {
+                double rankScore = currentRanking.rankScore;
+                if (verbose) {
+                    System.out.print("Rank " + rank + ": ");
+                    if (printScore) {
+                        System.out.print(formatter.format(rankScore));
+                    }
+                    System.out.print("\tVertex Id: " + currentRanking.originalPos);
+                        System.out.print(" (" + currentRanking.getRanked() + ")");
+                    System.out.println();
+                } else {
+                    System.out.print(rank + "\t");
+                     if (printScore) {
+                        System.out.print(formatter.format(rankScore));
+                    }
+                    System.out.println("\t" + currentRanking.originalPos);
+
+                }
+                total += rankScore;
+                rank++;
+            }
+
+            if (verbose) {
+                System.out.println("Total: " + formatter.format(total));
+            }
+    }
+
+    /**
+     * Allows the user to specify whether or not s/he wants the rankings to be normalized.
+     * In some cases, this will have no effect since the algorithm doesn't allow normalization
+     * as an option
+     * @param normalizeRankings {@code true} iff the ranking are to be normalized
+     */
+    public void setNormalizeRankings(boolean normalizeRankings) {
+        mNormalizeRankings = normalizeRankings;
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/BetweennessCentrality.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/BetweennessCentrality.java
new file mode 100644
index 0000000..e2e776b
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/BetweennessCentrality.java
@@ -0,0 +1,189 @@
+/*
+* Copyright (c) 2003, The JUNG Authors
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.importance;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Stack;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+
+/**
+ * Computes betweenness centrality for each vertex and edge in the graph. The result is that each vertex
+ * and edge has a UserData element of type MutableDouble whose key is 'centrality.BetweennessCentrality'.
+ * Note: Many social network researchers like to normalize the betweenness values by dividing the values by
+ * (n-1)(n-2)/2. The values given here are unnormalized.<p>
+ *
+ * A simple example of usage is:
+ * <pre>
+ * BetweennessCentrality ranker = new BetweennessCentrality(someGraph);
+ * ranker.evaluate();
+ * ranker.printRankings();
+ * </pre>
+ *
+ * Running time is: O(n^2 + nm).
+ * @see "Ulrik Brandes: A Faster Algorithm for Betweenness Centrality. Journal of Mathematical Sociology 25(2):163-177, 2001."
+ * @author Scott White
+ * @author Tom Nelson converted to jung2
+ */
+
+public class BetweennessCentrality<V,E> extends AbstractRanker<V,E> {
+
+    public static final String CENTRALITY = "centrality.BetweennessCentrality";
+
+    /**
+     * Constructor which initializes the algorithm
+     * @param g the graph whose nodes are to be analyzed
+     */
+    public BetweennessCentrality(Graph<V,E> g) {
+        initialize(g, true, true);
+    }
+
+    public BetweennessCentrality(Graph<V,E> g, boolean rankNodes) {
+        initialize(g, rankNodes, true);
+    }
+
+    public BetweennessCentrality(Graph<V,E> g, boolean rankNodes, boolean rankEdges)
+    {
+        initialize(g, rankNodes, rankEdges);
+    }
+    
+	protected void computeBetweenness(Graph<V,E> graph) {
+
+    	Map<V,BetweennessData> decorator = new HashMap<V,BetweennessData>();
+    	Map<V,Number> bcVertexDecorator = 
+    		vertexRankScores.getUnchecked(getRankScoreKey());
+    	bcVertexDecorator.clear();
+    	Map<E,Number> bcEdgeDecorator = 
+    		edgeRankScores.getUnchecked(getRankScoreKey());
+    	bcEdgeDecorator.clear();
+        
+        Collection<V> vertices = graph.getVertices();
+        
+        for (V s : vertices) {
+
+            initializeData(graph,decorator);
+
+            decorator.get(s).numSPs = 1;
+            decorator.get(s).distance = 0;
+
+            Stack<V> stack = new Stack<V>();
+            Queue<V> queue = new LinkedList<V>();
+            queue.add(s);
+
+            while (!queue.isEmpty()) {
+                V v = queue.remove();
+                stack.push(v);
+
+                for(V w : getGraph().getSuccessors(v)) {
+
+                    if (decorator.get(w).distance < 0) {
+                        queue.add(w);
+                        decorator.get(w).distance = decorator.get(v).distance + 1;
+                    }
+
+                    if (decorator.get(w).distance == decorator.get(v).distance + 1) {
+                        decorator.get(w).numSPs += decorator.get(v).numSPs;
+                        decorator.get(w).predecessors.add(v);
+                    }
+                }
+            }
+            
+            while (!stack.isEmpty()) {
+                V w = stack.pop();
+
+                for (V v : decorator.get(w).predecessors) {
+
+                    double partialDependency = (decorator.get(v).numSPs / decorator.get(w).numSPs);
+                    partialDependency *= (1.0 + decorator.get(w).dependency);
+                    decorator.get(v).dependency +=  partialDependency;
+                    E currentEdge = getGraph().findEdge(v, w);
+                    double edgeValue = bcEdgeDecorator.get(currentEdge).doubleValue();
+                    edgeValue += partialDependency;
+                    bcEdgeDecorator.put(currentEdge, edgeValue);
+                }
+                if (w != s) {
+                	double bcValue = bcVertexDecorator.get(w).doubleValue();
+                	bcValue += decorator.get(w).dependency;
+                	bcVertexDecorator.put(w, bcValue);
+                }
+            }
+        }
+
+        if(graph instanceof UndirectedGraph) {
+            for (V v : vertices) { 
+            	double bcValue = bcVertexDecorator.get(v).doubleValue();
+            	bcValue /= 2.0;
+            	bcVertexDecorator.put(v, bcValue);
+            }
+            for (E e : graph.getEdges()) {
+            	double bcValue = bcEdgeDecorator.get(e).doubleValue();
+            	bcValue /= 2.0;
+            	bcEdgeDecorator.put(e, bcValue);
+            }
+        }
+
+        for (V vertex : vertices) {
+            decorator.remove(vertex);
+        }
+    }
+
+    private void initializeData(Graph<V,E> g, Map<V,BetweennessData> decorator) {
+        for (V vertex : g.getVertices()) {
+
+        	Map<V,Number> bcVertexDecorator = vertexRankScores.getUnchecked(getRankScoreKey());
+        	if(bcVertexDecorator.containsKey(vertex) == false) {
+        		bcVertexDecorator.put(vertex, 0.0);
+        	}
+            decorator.put(vertex, new BetweennessData());
+        }
+        for (E e : g.getEdges()) {
+
+        	Map<E,Number> bcEdgeDecorator = edgeRankScores.getUnchecked(getRankScoreKey());
+        	if(bcEdgeDecorator.containsKey(e) == false) {
+        		bcEdgeDecorator.put(e, 0.0);
+        	}
+        }
+    }
+    
+    /**
+     * the user datum key used to store the rank scores
+     * @return the key
+     */
+    @Override
+    public String getRankScoreKey() {
+        return CENTRALITY;
+    }
+
+    @Override
+    public void step() {
+        computeBetweenness(getGraph());
+    }
+
+    class BetweennessData {
+        double distance;
+        double numSPs;
+        List<V> predecessors;
+        double dependency;
+
+        BetweennessData() {
+            distance = -1;
+            numSPs = 0;
+            predecessors = new ArrayList<V>();
+            dependency = 0;
+        }
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/KStepMarkov.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/KStepMarkov.java
new file mode 100644
index 0000000..717c30b
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/KStepMarkov.java
@@ -0,0 +1,135 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.importance;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import edu.uci.ics.jung.graph.DirectedGraph;
+
+
+/**
+ * Algorithm variant of <code>PageRankWithPriors</code> that computes the importance of a node based upon taking fixed-length random
+ * walks out from the root set and then computing the stationary probability of being at each node. Specifically, it computes
+ * the relative probability that the markov chain will spend at any particular node, given that it start in the root
+ * set and ends after k steps.
+ * <p>
+ * A simple example of usage is:
+ * <pre>
+ * KStepMarkov ranker = new KStepMarkov(someGraph,rootSet,6,null);
+ * ranker.evaluate();
+ * ranker.printRankings();
+ * </pre>
+ * <p>
+ *
+ * @author Scott White
+ * @author Tom Nelson - adapter to jung2
+ * @see "Algorithms for Estimating Relative Importance in Graphs by Scott White and Padhraic Smyth, 2003"
+ */
+public class KStepMarkov<V,E> extends RelativeAuthorityRanker<V,E> {
+    public final static String RANK_SCORE = "jung.algorithms.importance.KStepMarkovExperimental.RankScore";
+    private final static String CURRENT_RANK = "jung.algorithms.importance.KStepMarkovExperimental.CurrentRank";
+    private int mNumSteps;
+    HashMap<V,Number> mPreviousRankingsMap;
+
+    /**
+     * Construct the algorihm instance and initializes the algorithm.
+     * @param graph the graph to be analyzed
+     * @param priors the set of root nodes
+     * @param k positive integer parameter which controls the relative tradeoff between a distribution "biased" towards
+     * R and the steady-state distribution which is independent of where the Markov-process started. Generally values
+     * between 4-8 are reasonable
+     * @param edgeWeights the weight for each edge 
+     */
+    public KStepMarkov(DirectedGraph<V,E> graph, Set<V> priors, int k, Map<E,Number> edgeWeights) {
+        super.initialize(graph,true,false);
+        mNumSteps = k;
+        setPriors(priors);
+        initializeRankings();
+        if (edgeWeights == null) {
+            assignDefaultEdgeTransitionWeights();
+        } else {
+            setEdgeWeights(edgeWeights);
+        }
+        normalizeEdgeTransitionWeights();
+    }
+
+    /**
+     * The user datum key used to store the rank scores.
+     * @return the key
+     */
+    @Override
+    public String getRankScoreKey() {
+        return RANK_SCORE;
+    }
+
+    protected void incrementRankScore(V v, double rankValue) {
+    	double value = getVertexRankScore(v, RANK_SCORE);
+    	value += rankValue;
+    	setVertexRankScore(v, value, RANK_SCORE);
+    }
+
+    protected double getCurrentRankScore(V v) {
+    	return getVertexRankScore(v, CURRENT_RANK);
+    }
+
+    protected void setCurrentRankScore(V v, double rankValue) {
+    	setVertexRankScore(v, rankValue, CURRENT_RANK);
+    }
+
+    protected void initializeRankings() {
+         mPreviousRankingsMap = new HashMap<V,Number>();
+         for (V v : getVertices()) {
+            Set<V> priors = getPriors();
+            double numPriors = priors.size();
+
+            if (getPriors().contains(v)) {
+                setVertexRankScore(v, 1.0/ numPriors);
+                setCurrentRankScore(v, 1.0/ numPriors);
+                mPreviousRankingsMap.put(v,1.0/numPriors);
+            } else {
+                setVertexRankScore(v, 0);
+                setCurrentRankScore(v, 0);
+                mPreviousRankingsMap.put(v, 0);
+            }
+        }
+     }
+    @Override
+    public void step() {
+
+        for (int i=0;i<mNumSteps;i++) {
+            updateRankings();
+            for (V v : getVertices()) {
+                double currentRankScore = getCurrentRankScore(v);
+                incrementRankScore(v,currentRankScore);
+                mPreviousRankingsMap.put(v, currentRankScore);
+            }
+        }
+        normalizeRankings();
+    }
+
+    protected void updateRankings() {
+
+        for (V v : getVertices()) {
+
+            Collection<E> incomingEdges = getGraph().getInEdges(v);
+
+            double currentPageRankSum = 0;
+            for (E e : incomingEdges) {
+                double currentWeight = getEdgeWeight(e);
+                currentPageRankSum += 
+                	mPreviousRankingsMap.get(getGraph().getOpposite(v,e)).doubleValue()*currentWeight;
+            }
+            setCurrentRankScore(v,currentPageRankSum);
+        }
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/Ranking.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/Ranking.java
new file mode 100644
index 0000000..84cbd66
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/Ranking.java
@@ -0,0 +1,79 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.importance;
+
+
+/**
+ * Abstract data container for ranking objects. Stores common data relevant to both node and edge rankings, namely,
+ * the original position of the instance in the list and the actual ranking score.
+ * @author Scott White
+ */
+ at SuppressWarnings("rawtypes")
+public class Ranking<V> implements Comparable {
+    /**
+     * The original (0-indexed) position of the instance being ranked
+     */
+    public int originalPos;
+    /**
+     * The actual rank score (normally between 0 and 1)
+     */
+    public double rankScore;
+    
+    /**
+     * what is being ranked
+     */
+    private V ranked;
+
+    /**
+     * Constructor which allows values to be set on construction
+     * @param originalPos The original (0-indexed) position of the instance being ranked
+     * @param rankScore The actual rank score (normally between 0 and 1)
+     * @param ranked the vertex being ranked
+     */
+    public Ranking(int originalPos, double rankScore, V ranked) {
+        this.originalPos = originalPos;
+        this.rankScore = rankScore;
+        this.ranked = ranked;
+    }
+
+    /**
+     * Compares two ranking based on the rank score.
+     * @param other The other ranking
+     * @return -1 if the other ranking is higher, 0 if they are equal, and 1 if this ranking is higher
+     */
+    public int compareTo(Object other) {
+    	@SuppressWarnings("unchecked")
+		Ranking<V> otherRanking = (Ranking<V>) other;
+        return Double.compare(otherRanking.rankScore,rankScore);
+    }
+
+    /**
+     * Returns the rank score as a string.
+     * @return the stringified rank score
+     */
+    @Override
+    public String toString() {
+        return String.valueOf(rankScore);
+    }
+
+	/**
+	 * @return the ranked element
+	 */
+	public V getRanked() {
+		return ranked;
+	}
+
+	/**
+	 * @param ranked the ranked to set
+	 */
+	public void setRanked(V ranked) {
+		this.ranked = ranked;
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/RelativeAuthorityRanker.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/RelativeAuthorityRanker.java
new file mode 100644
index 0000000..262df48
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/RelativeAuthorityRanker.java
@@ -0,0 +1,73 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.importance;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * This class provides basic infrastructure for relative authority algorithms that compute the importance of nodes
+ * relative to one or more root nodes. The services provided are:
+ * <ul>
+ * <li>The set of root nodes (priors) is stored and maintained</li>
+ * <li>Getters and setters for the prior rank score are provided</li>
+ * </ul>
+ * 
+ * @author Scott White
+ */
+public abstract class RelativeAuthorityRanker<V,E> extends AbstractRanker<V,E> {
+    private Set<V> mPriors;
+    /**
+     * The default key used for the user datum key corresponding to prior rank scores.
+     */
+
+    protected Map<V,Number> priorRankScoreMap = new HashMap<V,Number>();
+    /**
+     * Cleans up all of the prior rank scores on finalize.
+     */
+    @Override
+    protected void finalizeIterations() {
+        super.finalizeIterations();
+        priorRankScoreMap.clear();
+    }
+
+    /**
+     * Retrieves the value of the prior rank score.
+     * @param v the root node (prior)
+     * @return the prior rank score
+     */
+    protected double getPriorRankScore(V v) {
+    	return priorRankScoreMap.get(v).doubleValue();
+
+    }
+
+    /**
+     * Allows the user to specify a value to set for the prior rank score
+     * @param v the root node (prior)
+     * @param value the score to set to
+     */
+    public void setPriorRankScore(V v, double value) {
+    	this.priorRankScoreMap.put(v, value);
+    }
+
+    /**
+     * Retrieves the set of priors.
+     * @return the set of root nodes (priors)
+     */
+    protected Set<V> getPriors() { return mPriors; }
+
+    /**
+     * Specifies which vertices are root nodes (priors).
+     * @param priors the root nodes
+     */
+    protected void setPriors(Set<V> priors) { mPriors = priors; }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/WeightedNIPaths.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/WeightedNIPaths.java
new file mode 100644
index 0000000..29d8a6e
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/importance/WeightedNIPaths.java
@@ -0,0 +1,196 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.importance;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.DirectedGraph;
+
+
+
+/**
+ * This algorithm measures the importance of nodes based upon both the number and length of disjoint paths that lead
+ * to a given node from each of the nodes in the root set. Specifically the formula for measuring the importance of a
+ * node is given by: I(t|R) = sum_i=1_|P(r,t)|_{alpha^|p_i|} where alpha is the path decay coefficient, p_i is path i
+ * and P(r,t) is a set of maximum-sized node-disjoint paths from r to t.
+ * <p>
+ * This algorithm uses heuristic breadth-first search to try and find the maximum-sized set of node-disjoint paths
+ * between two nodes. As such, it is not guaranteed to give exact answers.
+ * <p>
+ * A simple example of usage is:
+ * <pre>
+ * WeightedNIPaths ranker = new WeightedNIPaths(someGraph,2.0,6,rootSet);
+ * ranker.evaluate();
+ * ranker.printRankings();
+ * </pre>
+ * 
+ * @author Scott White
+ * @see "Algorithms for Estimating Relative Importance in Graphs by Scott White and Padhraic Smyth, 2003"
+ */
+public class WeightedNIPaths<V,E> extends AbstractRanker<V,E> {
+    public final static String WEIGHTED_NIPATHS_KEY = "jung.algorithms.importance.WEIGHTED_NIPATHS_KEY";
+    private double mAlpha;
+    private int mMaxDepth;
+    private Set<V> mPriors;
+    private Map<E,Number> pathIndices = new HashMap<E,Number>();
+    private Map<Object,V> roots = new HashMap<Object,V>();
+    private Map<V,Set<Number>> pathsSeenMap = new HashMap<V,Set<Number>>();
+    private Supplier<V> vertexFactory;
+    private Supplier<E> edgeFactory;
+
+    /**
+     * Constructs and initializes the algorithm.
+     * @param graph the graph whose nodes are being measured for their importance
+     * @param vertexFactory used to generate instances of V
+     * @param edgeFactory used to generate instances of E
+     * @param alpha the path decay coefficient (≥1); 2 is recommended
+     * @param maxDepth the maximal depth to search out from the root set
+     * @param priors the root set (starting vertices)
+     */
+    public WeightedNIPaths(DirectedGraph<V,E> graph, Supplier<V> vertexFactory,
+    		Supplier<E> edgeFactory, double alpha, int maxDepth, Set<V> priors) {
+        super.initialize(graph, true,false);
+        this.vertexFactory = vertexFactory;
+        this.edgeFactory = edgeFactory;
+        mAlpha = alpha;
+        mMaxDepth = maxDepth;
+        mPriors = priors;
+        for (V v : graph.getVertices()) {
+        	super.setVertexRankScore(v, 0.0);
+        }
+    }
+
+    protected void incrementRankScore(V v, double rankValue) {
+        setVertexRankScore(v, getVertexRankScore(v) + rankValue);
+    }
+
+    protected void computeWeightedPathsFromSource(V root, int depth) {
+
+        int pathIdx = 1;
+
+        for (E e : getGraph().getOutEdges(root)) {
+            this.pathIndices.put(e, pathIdx);
+            this.roots.put(e, root);
+            newVertexEncountered(pathIdx, getGraph().getEndpoints(e).getSecond(), root);
+            pathIdx++;
+        }
+
+        List<E> edges = new ArrayList<E>();
+
+        V virtualNode = vertexFactory.get();
+        getGraph().addVertex(virtualNode);
+        E virtualSinkEdge = edgeFactory.get();
+
+        getGraph().addEdge(virtualSinkEdge, virtualNode, root);
+        edges.add(virtualSinkEdge);
+
+        int currentDepth = 0;
+        while (currentDepth <= depth) {
+
+            double currentWeight = Math.pow(mAlpha, -1.0 * currentDepth);
+            for (E currentEdge : edges) { 
+                incrementRankScore(getGraph().getEndpoints(currentEdge).getSecond(),//
+                		currentWeight);
+            }
+
+            if ((currentDepth == depth) || (edges.size() == 0)) break;
+
+            List<E> newEdges = new ArrayList<E>();
+
+            for (E currentSourceEdge : edges) { //Iterator sourceEdgeIt = edges.iterator(); sourceEdgeIt.hasNext();) {
+                Number sourcePathIndex = this.pathIndices.get(currentSourceEdge);
+
+                // from the currentSourceEdge, get its opposite end
+                // then iterate over the out edges of that opposite end
+                V newDestVertex = getGraph().getEndpoints(currentSourceEdge).getSecond();
+                Collection<E> outs = getGraph().getOutEdges(newDestVertex);
+                for (E currentDestEdge : outs) {
+                	V destEdgeRoot = this.roots.get(currentDestEdge);
+                	V destEdgeDest = getGraph().getEndpoints(currentDestEdge).getSecond();
+
+                    if (currentSourceEdge == virtualSinkEdge) {
+                        newEdges.add(currentDestEdge);
+                        continue;
+                    }
+                    if (destEdgeRoot == root) {
+                        continue;
+                    }
+                    if (destEdgeDest == getGraph().getEndpoints(currentSourceEdge).getFirst()) {//currentSourceEdge.getSource()) {
+                        continue;
+                    }
+                    Set<Number> pathsSeen = this.pathsSeenMap.get(destEdgeDest);
+
+                    if (pathsSeen == null) {
+                        newVertexEncountered(sourcePathIndex.intValue(), destEdgeDest, root);
+                    } else if (roots.get(destEdgeDest) != root) {
+                        roots.put(destEdgeDest,root);
+                        pathsSeen.clear();
+                        pathsSeen.add(sourcePathIndex);
+                    } else if (!pathsSeen.contains(sourcePathIndex)) {
+                        pathsSeen.add(sourcePathIndex);
+                    } else {
+                        continue;
+                    }
+
+                    this.pathIndices.put(currentDestEdge, sourcePathIndex);
+                    this.roots.put(currentDestEdge, root);
+                    newEdges.add(currentDestEdge);
+                }
+            }
+
+            edges = newEdges;
+            currentDepth++;
+        }
+
+        getGraph().removeVertex(virtualNode);
+    }
+
+    private void newVertexEncountered(int sourcePathIndex, V dest, V root) {
+        Set<Number> pathsSeen = new HashSet<Number>();
+        pathsSeen.add(sourcePathIndex);
+        this.pathsSeenMap.put(dest, pathsSeen);
+        roots.put(dest, root);
+    }
+
+    @Override
+    public void step() {
+        for (V v : mPriors) {
+            computeWeightedPathsFromSource(v, mMaxDepth);
+        }
+
+        normalizeRankings();
+//        return 0;
+    }
+    
+    /**
+     * Given a node, returns the corresponding rank score. This implementation of <code>getRankScore</code> assumes
+     * the decoration representing the rank score is of type <code>MutableDouble</code>.
+     * @return  the rank score for this node
+     */
+    @Override
+    public String getRankScoreKey() {
+        return WEIGHTED_NIPATHS_KEY;
+    }
+
+    @Override
+    protected void onFinalize(Object udc) {
+    	pathIndices.remove(udc);
+    	roots.remove(udc);
+    	pathsSeenMap.remove(udc);
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/AbstractLayout.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/AbstractLayout.java
new file mode 100644
index 0000000..10d4e7c
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/AbstractLayout.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ * Created on Jul 7, 2003
+ * 
+ */
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+import java.util.ConcurrentModificationException;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * Abstract class for implementations of {@code Layout}.  It handles some of the
+ * basic functions: storing coordinates, maintaining the dimensions, initializing
+ * the locations, maintaining locked vertices.
+ * 
+ * @author Danyel Fisher, Scott White
+ * @author Tom Nelson - converted to jung2
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+abstract public class AbstractLayout<V, E> implements Layout<V,E> {
+
+    /**
+     * A set of vertices that are fixed in place and not affected by the layout algorithm
+     */
+	private Set<V> dontmove = new HashSet<V>();
+
+	protected Dimension size;
+	protected Graph<V, E> graph;
+	protected boolean initialized;
+
+    protected LoadingCache<V, Point2D> locations =
+    	CacheBuilder.newBuilder().build(new CacheLoader<V, Point2D>() {
+	    	public Point2D load(V vertex) {
+	    		return new Point2D.Double();
+	    	}
+    });
+
+	/**
+	 * Creates an instance for {@code graph} which does not initialize the vertex locations.
+	 * 
+	 * @param graph the graph on which the layout algorithm is to operate
+	 */
+	protected AbstractLayout(Graph<V, E> graph) {
+	    if (graph == null) 
+	    {
+	        throw new IllegalArgumentException("Graph must be non-null");
+	    }
+		this.graph = graph;
+	}
+	
+	/**
+	 * Creates an instance for {@code graph} which initializes the vertex locations
+	 * using {@code initializer}.
+	 * 
+	 * @param graph the graph on which the layout algorithm is to operate
+	 * @param initializer specifies the starting positions of the vertices
+	 */
+    protected AbstractLayout(Graph<V,E> graph, Function<V,Point2D> initializer) {
+		this.graph = graph;
+		Function<V, Point2D> chain = 
+			Functions.<V,Point2D,Point2D>compose(
+					new Function<Point2D,Point2D>(){
+						public Point2D apply(Point2D p) {
+							return (Point2D)p.clone();
+						}}, 
+					initializer
+					);
+		this.locations = CacheBuilder.newBuilder().build(CacheLoader.from(chain)); 
+		initialized = true;
+	}
+	
+	/**
+	 * Creates an instance for {@code graph} which sets the size of the layout to {@code size}.
+	 * 
+	 * @param graph the graph on which the layout algorithm is to operate
+     * @param size the dimensions of the region in which the layout algorithm will place vertices
+	 */
+	protected AbstractLayout(Graph<V,E> graph, Dimension size) {
+		this.graph = graph;
+		this.size = size;
+	}
+	
+	/**
+	 * Creates an instance for {@code graph} which initializes the vertex locations
+	 * using {@code initializer} and sets the size of the layout to {@code size}.
+	 * 
+	 * @param graph the graph on which the layout algorithm is to operate
+	 * @param initializer specifies the starting positions of the vertices
+     * @param size the dimensions of the region in which the layout algorithm will place vertices
+	 */
+    protected AbstractLayout(Graph<V,E> graph, Function<V,Point2D> initializer, Dimension size) {
+		this.graph = graph;
+		Function<V, Point2D> chain = 
+			Functions.<V,Point2D,Point2D>compose(
+					new Function<Point2D,Point2D>(){
+						public Point2D apply(Point2D p) {
+							return (Point2D)p.clone();
+						}}, 
+					initializer
+					);
+		this.locations = CacheBuilder.newBuilder().build(CacheLoader.from(chain)); 
+		this.size = size;
+	}
+    
+    public void setGraph(Graph<V,E> graph) {
+        this.graph = graph;
+        if(size != null && graph != null) {
+        	initialize();
+        }
+    }
+    
+	/**
+	 * When a visualization is resized, it presumably wants to fix the
+	 * locations of the vertices and possibly to reinitialize its data. The
+	 * current method calls <tt>initializeLocations</tt> followed by <tt>initialize_local</tt>.
+	 */
+	public void setSize(Dimension size) {
+		
+		if(size != null && graph != null) {
+			
+			Dimension oldSize = this.size;
+			this.size = size;
+			initialize();
+			
+			if(oldSize != null) {
+				adjustLocations(oldSize, size);
+			}
+		}
+	}
+	
+	private void adjustLocations(Dimension oldSize, Dimension size) {
+
+		int xOffset = (size.width - oldSize.width) / 2;
+		int yOffset = (size.height - oldSize.height) / 2;
+
+		// now, move each vertex to be at the new screen center
+		while(true) {
+		    try {
+                for(V v : getGraph().getVertices()) {
+		            offsetVertex(v, xOffset, yOffset);
+		        }
+		        break;
+		    } catch(ConcurrentModificationException cme) {
+		    }
+		}
+	}
+    
+    public boolean isLocked(V v) {
+        return dontmove.contains(v);
+    }
+    
+    public void setInitializer(Function<V,Point2D> initializer) {
+    	if(this.equals(initializer)) {
+    		throw new IllegalArgumentException("Layout cannot be initialized with itself");
+    	}
+		Function<V, Point2D> chain = 
+			Functions.<V,Point2D,Point2D>compose(
+					new Function<Point2D,Point2D>(){
+						public Point2D apply(Point2D p) {
+							return (Point2D)p.clone();
+						}}, 
+					initializer
+					);
+		this.locations = CacheBuilder.newBuilder().build(CacheLoader.from(chain)); 
+    	initialized = true;
+    }
+    
+	/**
+	 * Returns the current size of the visualization space, accoring to the
+	 * last call to resize().
+	 * 
+	 * @return the current size of the screen
+	 */
+	public Dimension getSize() {
+		return size;
+	}
+
+	/**
+	 * 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.
+	 */
+	private Point2D getCoordinates(V v) {
+        return locations.getUnchecked(v);
+	}
+	
+	public Point2D apply(V v) {
+		return getCoordinates(v);
+	}
+	
+	/**
+	 * Returns the x coordinate of the vertex from the Coordinates object.
+	 * in most cases you will be better off calling transform(v).
+	 * 
+	 * @param v the vertex whose x coordinate is to be returned
+	 * @return the x coordinate of {@code v}
+	 */
+	public double getX(V v) {
+        Preconditions.checkNotNull(getCoordinates(v), "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 transform(v).
+	 * 
+	 * @param v the vertex whose y coordinate is to be returned
+	 * @return the y coordinate of {@code v}
+	 */
+	public double getY(V v) {
+        Preconditions.checkNotNull(getCoordinates(v), "Cannot getY for an unmapped vertex "+v);
+        return getCoordinates(v).getY();
+	}
+	
+	/**
+	 * @param v the vertex whose coordinates are to be offset
+	 * @param xOffset the change to apply to this vertex's x coordinate
+	 * @param yOffset the change to apply to this vertex's y coordinate
+	 */
+	protected void offsetVertex(V v, double xOffset, double yOffset) {
+		Point2D c = getCoordinates(v);
+        c.setLocation(c.getX()+xOffset, c.getY()+yOffset);
+		setLocation(v, c);
+	}
+
+	/**
+	 * @return the graph that this layout operates on
+	 */
+	public Graph<V, E> getGraph() {
+	    return graph;
+	}
+	
+	/**
+	 * Forcibly moves a vertex to the (x,y) location by setting its x and y
+	 * locations to the specified 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.
+	 * @param picked the vertex whose location is being set
+	 * @param x the x coordinate of the location to set
+	 * @param y the y coordinate of the location to set
+	 */
+	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);
+	}
+
+	/**
+	 * Locks {@code v} in place if {@code state} is {@code true}, otherwise unlocks it.
+	 * @param v the vertex whose position is to be (un)locked
+	 * @param state {@code true} if the vertex is to be locked, {@code false} if to be unlocked
+	 */
+	public void lock(V v, boolean state) {
+		if(state == true) 
+		    dontmove.add(v);
+		else 
+		    dontmove.remove(v);
+	}
+	
+	/**
+	 * @param lock {@code true} to lock all vertices in place, {@code false} to unlock all vertices
+	 */
+	public void lock(boolean lock) {
+		for(V v : graph.getVertices()) {
+			lock(v, lock);
+		}
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/AggregateLayout.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/AggregateLayout.java
new file mode 100644
index 0000000..7d338a1
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/AggregateLayout.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ * 
+ * 
+ */
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Dimension;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * A {@code Layout} implementation that combines 
+ * multiple other layouts so that they may be manipulated
+ * as one layout. The relaxer thread will step each layout
+ * in sequence.
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+public class AggregateLayout<V, E> implements Layout<V,E>, IterativeContext {
+
+	protected Layout<V,E> delegate;
+	protected Map<Layout<V,E>,Point2D> layouts = new HashMap<Layout<V,E>,Point2D>();
+
+	/**
+	 * Creates an instance backed by the specified {@code delegate}.
+	 * @param delegate the layout to which this instance is delegating
+	 */
+	public AggregateLayout(Layout<V, E> delegate) {
+		this.delegate = delegate;
+	}
+
+	/**
+	 * @return the delegate
+	 */
+	public Layout<V, E> getDelegate() {
+		return delegate;
+	}
+
+	/**
+	 * @param delegate the delegate to set
+	 */
+	public void setDelegate(Layout<V, E> delegate) {
+		this.delegate = delegate;
+	}
+
+	/**
+	 * Adds the passed layout as a sublayout, and specifies
+	 * the center of where this sublayout should appear.
+	 * @param layout the layout algorithm to use as a sublayout
+	 * @param center the center of the coordinates for the sublayout
+	 */
+	public void put(Layout<V,E> layout, Point2D center) {
+		layouts.put(layout,center);
+	}
+	
+	/**
+	 * @param layout the layout whose center is to be returned
+	 * @return the center of the passed layout
+	 */
+	public Point2D get(Layout<V,E> layout) {
+		return layouts.get(layout);
+	}
+	
+	/**
+	 * Removes {@code layout} from this instance.
+	 * @param layout the layout to remove
+	 */
+	public void remove(Layout<V,E> layout) {
+		layouts.remove(layout);
+	}
+	
+	/**
+	 * Removes all layouts from this instance.
+	 */
+	public void removeAll() {
+		layouts.clear();
+	}
+	
+	public Graph<V, E> getGraph() {
+		return delegate.getGraph();
+	}
+
+	public Dimension getSize() {
+		return delegate.getSize();
+	}
+
+	public void initialize() {
+		delegate.initialize();
+		for(Layout<V,E> layout : layouts.keySet()) {
+			layout.initialize();
+		}
+	}
+
+	/**
+	 * @param v the vertex whose locked state is to be returned
+	 * @return true if v is locked in any of the layouts, and false otherwise
+	 */
+	public boolean isLocked(V v) {
+		for(Layout<V,E> layout : layouts.keySet()) {
+			if (layout.isLocked(v)) {
+				return true;
+			}
+		}
+		return delegate.isLocked(v);
+	}
+
+	/**
+	 * Locks this vertex in the main layout and in any sublayouts whose graph contains
+	 * this vertex.
+	 * @param v the vertex whose locked state is to be set
+	 * @param state {@code true} if the vertex is to be locked, and {@code false} if unlocked
+	 */
+	public void lock(V v, boolean state) {
+		for(Layout<V,E> layout : layouts.keySet()) {
+			if(layout.getGraph().getVertices().contains(v)) {
+				layout.lock(v, state);
+			}
+		}
+		delegate.lock(v, state);
+	}
+
+	public void reset() {
+		for(Layout<V,E> layout : layouts.keySet()) {
+			layout.reset();
+		}
+		delegate.reset();
+	}
+
+	public void setGraph(Graph<V, E> graph) {
+		delegate.setGraph(graph);
+	}
+
+	public void setInitializer(Function<V, Point2D> initializer) {
+		delegate.setInitializer(initializer);
+	}
+
+	public void setLocation(V v, Point2D location) {
+		boolean wasInSublayout = false;
+		for(Layout<V,E> layout : layouts.keySet()) {
+			if(layout.getGraph().getVertices().contains(v)) {
+				Point2D center = layouts.get(layout);
+				// transform by the layout itself, but offset to the
+				// center of the sublayout
+				Dimension d = layout.getSize();
+
+				AffineTransform at = 
+					AffineTransform.getTranslateInstance(-center.getX()+d.width/2,-center.getY()+d.height/2);
+				Point2D localLocation = at.transform(location, null);
+				layout.setLocation(v, localLocation);
+				wasInSublayout = true;
+			}
+		}
+		if(wasInSublayout == false && getGraph().getVertices().contains(v)) {
+			delegate.setLocation(v, location);
+		}
+	}
+
+	public void setSize(Dimension d) {
+		delegate.setSize(d);
+	}
+	
+	/**
+	 * @return a map from each {@code Layout} instance to its center point.
+	 */
+	public Map<Layout<V,E>,Point2D> getLayouts() {
+		return layouts;
+	}
+
+	/**
+	 * Returns the location of the vertex.  The location is specified first
+	 * by the sublayouts, and then by the base layout if no sublayouts operate
+	 * on this vertex.
+	 * @return the location of the vertex
+	 */
+	public Point2D apply(V v) {
+		boolean wasInSublayout = false;
+		for(Layout<V,E> layout : layouts.keySet()) {
+			if(layout.getGraph().getVertices().contains(v)) {
+				wasInSublayout = true;
+				Point2D center = layouts.get(layout);
+				// transform by the layout itself, but offset to the
+				// center of the sublayout
+				Dimension d = layout.getSize();
+				AffineTransform at = 
+					AffineTransform.getTranslateInstance(center.getX()-d.width/2,
+							center.getY()-d.height/2);
+				return at.transform(layout.apply(v),null);
+			}
+		}
+		if(wasInSublayout == false) {
+			return delegate.apply(v);
+		}
+		return null;
+	
+	}
+
+	/**
+	 * @return {@code true} iff the delegate layout and all sublayouts are done
+	 */
+	public boolean done() {
+		for (Layout<V,E> layout : layouts.keySet()) {
+			if (layout instanceof IterativeContext) {
+				if (! ((IterativeContext) layout).done() ) {
+					return false;
+				}
+			}
+		}
+		if(delegate instanceof IterativeContext) {
+			return ((IterativeContext)delegate).done();
+		}
+		return true;
+	}
+
+	/**
+	 * Call step on any sublayout that is also an IterativeContext and is not done
+	 */
+	public void step() {
+		for(Layout<V,E> layout : layouts.keySet()) {
+			if(layout instanceof IterativeContext) {
+				IterativeContext context = (IterativeContext)layout;
+				if(context.done() == false) {
+					context.step();
+				}
+			}
+		}
+		if(delegate instanceof IterativeContext) {
+			IterativeContext context = (IterativeContext)delegate;
+			if(context.done() == false) {
+				context.step();
+			}
+		}
+	}
+	
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/BalloonLayout.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/BalloonLayout.java
new file mode 100644
index 0000000..9badb22
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/BalloonLayout.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 9, 2005
+ */
+
+package edu.uci.ics.jung.algorithms.layout;
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import edu.uci.ics.jung.graph.Forest;
+import edu.uci.ics.jung.graph.util.TreeUtils;
+
+/**
+ * A {@code Layout} implementation that assigns positions to {@code Tree} or 
+ * {@code Forest} vertices using associations with nested circles ("balloons").  
+ * A balloon is nested inside another balloon if the first balloon's subtree
+ * is a subtree of the second balloon's subtree.
+ * 
+ * @author Tom Nelson 
+ *  
+ */
+public class BalloonLayout<V,E> extends TreeLayout<V,E> {
+
+    protected LoadingCache<V, PolarPoint> polarLocations
+    	= CacheBuilder.newBuilder().build(new CacheLoader<V, PolarPoint>() {
+    		public PolarPoint load(V vertex) {
+    			return new PolarPoint();
+    		}
+	});
+    
+    protected Map<V,Double> radii = new HashMap<V,Double>();
+    
+    /**
+     * Creates an instance based on the input forest.
+     * @param g the forest on which this layout will operate
+     */
+    public BalloonLayout(Forest<V,E> g) 
+    {
+        super(g);
+    }
+    
+    protected void setRootPolars() 
+    {
+        List<V> roots = TreeUtils.getRoots(graph);
+        if(roots.size() == 1) {
+    		// its a Tree
+    		V root = roots.get(0);
+    		setRootPolar(root);
+            setPolars(new ArrayList<V>(graph.getChildren(root)),
+                    getCenter(), getSize().width/2);
+    	} else if (roots.size() > 1) {
+    		// its a Forest
+    		setPolars(roots, getCenter(), getSize().width/2);
+    	}
+    }
+    
+    protected void setRootPolar(V root) {
+    	PolarPoint pp = new PolarPoint(0,0);
+    	Point2D p = getCenter();
+    	polarLocations.put(root, pp);
+    	locations.put(root, p);
+    }
+    
+
+    protected void setPolars(List<V> kids, Point2D parentLocation, double parentRadius) {
+
+    	int childCount = kids.size();
+    	if(childCount == 0) return;
+    	// handle the 1-child case with 0 limit on angle.
+    	double angle = Math.max(0, Math.PI / 2 * (1 - 2.0/childCount));
+    	double childRadius = parentRadius*Math.cos(angle) / (1 + Math.cos(angle));
+    	double radius = parentRadius - childRadius;
+
+    	double rand = Math.random();
+
+    	for(int i=0; i< childCount; i++) {
+    		V child = kids.get(i);
+    		double theta = i* 2*Math.PI/childCount + rand;
+    		radii.put(child, childRadius);
+    		
+    		PolarPoint pp = new PolarPoint(theta, radius);
+    		polarLocations.put(child, pp);
+    		
+    		Point2D p = PolarPoint.polarToCartesian(pp);
+    		p.setLocation(p.getX()+parentLocation.getX(), p.getY()+parentLocation.getY());
+    		locations.put(child, p);
+    		setPolars(new ArrayList<V>(graph.getChildren(child)), p, childRadius);
+    	}
+    }
+
+    @Override
+    public void setSize(Dimension size) {
+    	this.size = size;
+    	setRootPolars();
+    }
+
+	/**
+	 * @param v the vertex whose center is to be returned
+	 * @return the coordinates of {@code v}'s parent, or the center of this layout's area if it's a root.
+	 */
+	public Point2D getCenter(V v) {
+		V parent = graph.getParent(v);
+		if(parent == null) {
+			return getCenter();
+		}
+		return locations.getUnchecked(parent);
+	}
+
+	@Override
+    public void setLocation(V v, Point2D location) {
+		Point2D c = getCenter(v);
+		Point2D pv = new Point2D.Double(location.getX()-c.getX(),location.getY()-c.getY());
+		PolarPoint newLocation = PolarPoint.cartesianToPolar(pv);
+		polarLocations.getUnchecked(v).setLocation(newLocation);
+		
+		Point2D center = getCenter(v);
+		pv.setLocation(pv.getX()+center.getX(), pv.getY()+center.getY());
+		locations.put(v, pv);
+	}
+
+	@Override
+    public Point2D apply(V v) {
+		return locations.getUnchecked(v);
+	}
+
+	/**
+	 * @return the radii
+	 */
+	public Map<V, Double> getRadii() {
+		return radii;
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/CircleLayout.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/CircleLayout.java
new file mode 100644
index 0000000..5aad08e
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/CircleLayout.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Dec 4, 2003
+ */
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import edu.uci.ics.jung.graph.Graph;
+
+
+
+/**
+ * A {@code Layout} implementation that positions vertices equally spaced on a regular circle.
+ *
+ * @author Masanori Harada
+ */
+public class CircleLayout<V, E> extends AbstractLayout<V,E> {
+
+	private double radius;
+	private List<V> vertex_ordered_list;
+	
+    protected LoadingCache<V, CircleVertexData> circleVertexDatas =
+    	CacheBuilder.newBuilder().build(new CacheLoader<V, CircleVertexData>() {
+	    	public CircleVertexData load(V vertex) {
+	    		return new CircleVertexData();
+	    	}
+    });
+
+	public CircleLayout(Graph<V,E> g) {
+		super(g);
+	}
+
+	/**
+	 * @return the radius of the circle.
+	 */
+	public double getRadius() {
+		return radius;
+	}
+
+	/**
+	 * Sets the radius of the circle.  Must be called before {@code initialize()} is called.
+	 * @param radius the radius of the circle
+	 */
+	public void setRadius(double radius) {
+		this.radius = radius;
+	}
+
+	/**
+	 * Sets the order of the vertices in the layout according to the ordering
+	 * specified by {@code comparator}.
+	 * @param comparator the comparator to use to order the vertices
+	 */
+	public void setVertexOrder(Comparator<V> comparator)
+	{
+	    if (vertex_ordered_list == null)
+	        vertex_ordered_list = new ArrayList<V>(getGraph().getVertices());
+	    Collections.sort(vertex_ordered_list, comparator);
+	}
+
+    /**
+     * Sets the order of the vertices in the layout according to the ordering
+     * of {@code vertex_list}.
+     * @param vertex_list a list specifying the ordering of the vertices
+     */
+	public void setVertexOrder(List<V> vertex_list)
+	{
+	    if (!vertex_list.containsAll(getGraph().getVertices())) 
+	        throw new IllegalArgumentException("Supplied list must include " +
+	        		"all vertices of the graph");
+	    this.vertex_ordered_list = vertex_list;
+	}
+	
+	public void reset() {
+		initialize();
+	}
+
+	public void initialize() 
+	{
+		Dimension d = getSize();
+		
+		if (d != null) 
+		{
+		    if (vertex_ordered_list == null) 
+		        setVertexOrder(new ArrayList<V>(getGraph().getVertices()));
+
+			double height = d.getHeight();
+			double width = d.getWidth();
+
+			if (radius <= 0) {
+				radius = 0.45 * (height < width ? height : width);
+			}
+
+			int i = 0;
+			for (V v : vertex_ordered_list)
+			{
+				Point2D coord = apply(v);
+
+				double angle = (2 * Math.PI * i) / vertex_ordered_list.size();
+
+				coord.setLocation(Math.cos(angle) * radius + width / 2,
+						Math.sin(angle) * radius + height / 2);
+
+				CircleVertexData data = getCircleData(v);
+				data.setAngle(angle);
+				i++;
+			}
+		}
+	}
+
+	protected CircleVertexData getCircleData(V v) {
+		return circleVertexDatas.getUnchecked(v);
+	}
+
+	protected static class CircleVertexData {
+		private double angle;
+
+		protected double getAngle() {
+			return angle;
+		}
+
+		protected void setAngle(double angle) {
+			this.angle = angle;
+		}
+
+		@Override
+		public String toString() {
+			return "CircleVertexData: angle=" + angle;
+		}
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/DAGLayout.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/DAGLayout.java
new file mode 100644
index 0000000..0600693
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/DAGLayout.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Dec 4, 2003
+ */
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+import java.util.HashMap;
+import java.util.Map;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * An implementation of {@code Layout} suitable for tree-like directed
+ * acyclic graphs. Parts of it will probably not terminate if the graph is
+ * cyclic! The layout will result in directed edges pointing generally upwards.
+ * Any vertices with no successors are considered to be level 0, and tend
+ * towards the top of the layout. Any vertex has a level one greater than the
+ * maximum level of all its successors.
+ *
+ *
+ * @author John Yesberg
+ */
+public class DAGLayout<V, E> extends SpringLayout<V,E> {
+
+    /**
+     * Each vertex has a minimumLevel. Any vertex with no successors has
+     * minimumLevel of zero. The minimumLevel of any vertex must be strictly
+     * greater than the minimumLevel of its parents. (Vertex A is a parent of
+     * Vertex B iff there is an edge from B to A.) Typically, a vertex will
+     * have a minimumLevel which is one greater than the minimumLevel of its
+     * parent's. However, if the vertex has two parents, its minimumLevel will
+     * be one greater than the maximum of the parents'. We need to calculate
+     * the minimumLevel for each vertex. When we layout the graph, vertices
+     * cannot be drawn any higher than the minimumLevel. The graphHeight of a
+     * graph is the greatest minimumLevel that is used. We will modify the
+     * SpringLayout calculations so that nodes cannot move above their assigned
+     * minimumLevel.
+     */
+	private Map<V,Number> minLevels = new HashMap<V,Number>();
+	// Simpler than the "pair" technique.
+	static int graphHeight;
+	static int numRoots;
+	final double SPACEFACTOR = 1.3;
+	// How much space do we allow for additional floating at the bottom.
+	final double LEVELATTRACTIONRATE = 0.8;
+
+	/**
+	 * A bunch of parameters to help work out when to stop quivering.
+	 *
+	 * If the MeanSquareVel(ocity) ever gets below the MSV_THRESHOLD, then we
+	 * will start a final cool-down phase of COOL_DOWN_INCREMENT increments. If
+	 * the MeanSquareVel ever exceeds the threshold, we will exit the cool down
+	 * phase, and continue looking for another opportunity.
+	 */
+	final double MSV_THRESHOLD = 10.0;
+	double meanSquareVel;
+	boolean stoppingIncrements = false;
+	int incrementsLeft;
+	final int COOL_DOWN_INCREMENTS = 200;
+
+	public DAGLayout(Graph<V,E> g) {
+		super(g);
+	}
+
+	/**
+	 * Calculates the level of each vertex in the graph. Level 0 is
+	 * allocated to each vertex with no successors. Level n+1 is allocated to
+	 * any vertex whose successors' maximum level is n.
+	 */
+	public void setRoot() {
+		numRoots = 0;
+		Graph<V, E> g = getGraph();
+		for(V v : g.getVertices()) {
+			if (g.getSuccessors(v).isEmpty()) {
+				setRoot(v);
+				numRoots++;
+			}
+		}
+	}
+
+	/**
+	 * Set vertex v to be level 0.
+	 * @param v the vertex to set as root
+	 */
+	public void setRoot(V v) {
+		minLevels.put(v, new Integer(0));
+		// set all the levels.
+		propagateMinimumLevel(v);
+	}
+
+	/**
+	 * A recursive method for allocating the level for each vertex. Ensures
+	 * that all predecessors of v have a level which is at least one greater
+	 * than the level of v.
+	 *
+	 * @param v the vertex whose minimum level is to be calculated
+	 */
+	public void propagateMinimumLevel(V v) {
+		int level = minLevels.get(v).intValue();
+		for(V child : getGraph().getPredecessors(v)) {
+			int oldLevel, newLevel;
+			Number o = minLevels.get(child);
+			if (o != null)
+				oldLevel = o.intValue();
+			else
+				oldLevel = 0;
+			newLevel = Math.max(oldLevel, level + 1);
+			minLevels.put(child, new Integer(newLevel));
+
+			if (newLevel > graphHeight)
+				graphHeight = newLevel;
+			propagateMinimumLevel(child);
+		}
+	}
+
+	/**
+	 * Sets a random location for a vertex within the dimensions of the space.
+	 *
+	 * @param v the vertex whose position is to be set
+	 * @param coord the coordinates of the vertex once the position has been set
+	 * @param d the dimensions of the space
+	 */
+	private void initializeLocation(
+		V v,
+		Point2D coord,
+		Dimension d) {
+
+		int level = minLevels.get(v).intValue();
+		int minY = (int) (level * d.getHeight() / (graphHeight * SPACEFACTOR));
+		double x = Math.random() * d.getWidth();
+		double y = Math.random() * (d.getHeight() - minY) + minY;
+		coord.setLocation(x,y);
+	}
+
+	@Override
+	public void setSize(Dimension size) {
+		super.setSize(size);
+		for(V v : getGraph().getVertices()) {
+			initializeLocation(v,apply(v),getSize());
+		}
+	}
+
+	/**
+	 * Had to override this one as well, to ensure that setRoot() is called.
+	 */
+	@Override
+	public void initialize() {
+		super.initialize();
+		setRoot();
+	}
+
+	/**
+	 * Override the moveNodes() method from SpringLayout. The only change we
+	 * need to make is to make sure that nodes don't float higher than the minY
+	 * coordinate, as calculated by their minimumLevel.
+	 */
+	@Override
+	protected void moveNodes() {
+		// Dimension d = currentSize;
+		double oldMSV = meanSquareVel;
+		meanSquareVel = 0;
+
+		synchronized (getSize()) {
+
+			for(V v : getGraph().getVertices()) {
+				if (isLocked(v))
+					continue;
+				SpringLayout.SpringVertexData vd = springVertexData.getUnchecked(v);
+				Point2D xyd = apply(v);
+
+				int width = getSize().width;
+				int height = getSize().height;
+
+				// (JY addition: three lines are new)
+				int level =
+					minLevels.get(v).intValue();
+				int minY = (int) (level * height / (graphHeight * SPACEFACTOR));
+				int maxY =
+					level == 0
+						? (int) (height / (graphHeight * SPACEFACTOR * 2))
+						: height;
+
+				// JY added 2* - double the sideways repulsion.
+				vd.dx += 2 * vd.repulsiondx + vd.edgedx;
+				vd.dy += vd.repulsiondy + vd.edgedy;
+
+				// JY Addition: Attract the vertex towards it's minimumLevel
+				// height.
+				double delta = xyd.getY() - minY;
+				vd.dy -= delta * LEVELATTRACTIONRATE;
+				if (level == 0)
+					vd.dy -= delta * LEVELATTRACTIONRATE;
+				// twice as much at the top.
+
+				// JY addition:
+				meanSquareVel += (vd.dx * vd.dx + vd.dy * vd.dy);
+
+				// keeps nodes from moving any faster than 5 per time unit
+				xyd.setLocation(xyd.getX()+Math.max(-5, Math.min(5, vd.dx)) , xyd.getY()+Math.max(-5, Math.min(5, vd.dy)) );
+
+				if (xyd.getX() < 0) {
+					xyd.setLocation(0, xyd.getY());
+				} else if (xyd.getX() > width) {
+					xyd.setLocation(width, xyd.getY());
+				}
+
+				// (JY addition: These two lines replaced 0 with minY)
+				if (xyd.getY() < minY) {
+					xyd.setLocation(xyd.getX(), minY);
+					// (JY addition: replace height with maxY)
+				} else if (xyd.getY() > maxY) {
+					xyd.setLocation(xyd.getX(), maxY);
+				}
+
+				// (JY addition: if there's only one root, anchor it in the
+				// middle-top of the screen)
+				if (numRoots == 1 && level == 0) {
+					xyd.setLocation(width/2, xyd.getY());
+				}
+			}
+		}
+		//System.out.println("MeanSquareAccel="+meanSquareVel);
+		if (!stoppingIncrements
+			&& Math.abs(meanSquareVel - oldMSV) < MSV_THRESHOLD) {
+			stoppingIncrements = true;
+			incrementsLeft = COOL_DOWN_INCREMENTS;
+		} else if (
+			stoppingIncrements
+				&& Math.abs(meanSquareVel - oldMSV) <= MSV_THRESHOLD) {
+			incrementsLeft--;
+			if (incrementsLeft <= 0)
+				incrementsLeft = 0;
+		}
+	}
+
+	/**
+	 * Override incrementsAreDone so that we can eventually stop.
+	 */
+	@Override
+	public boolean done() {
+		if (stoppingIncrements && incrementsLeft == 0)
+			return true;
+		else
+			return false;
+	}
+
+	/**
+	 * Override forceMove so that if someone moves a node, we can re-layout
+	 * everything.
+     * @param picked the vertex whose location is to be set
+     * @param x the x coordinate of the location to set
+     * @param y the y coordinate of the location to set
+	 */
+	@Override
+	public void setLocation(V picked, double x, double y) {
+		Point2D coord = apply(picked);
+		coord.setLocation(x,y);
+		stoppingIncrements = false;
+	}
+
+	/**
+	 * Override forceMove so that if someone moves a node, we can re-layout
+	 * everything.
+     * @param picked the vertex whose location is to be set
+     * @param p the location to set
+	 */
+	@Override
+	public void setLocation(V picked, Point2D p) {
+		setLocation(picked, p.getX(), p.getY());
+	}
+
+	/**
+	 * Overridden relaxEdges. This one reduces the effect of edges between
+	 * greatly different levels.
+	 *
+	 */
+	@Override
+	protected void relaxEdges() {
+		for(E e : getGraph().getEdges()) {
+		    Pair<V> endpoints = getGraph().getEndpoints(e);
+			V v1 = endpoints.getFirst();
+			V v2 = endpoints.getSecond();
+
+			Point2D p1 = apply(v1);
+			Point2D p2 = apply(v2);
+			double vx = p1.getX() - p2.getX();
+			double vy = p1.getY() - p2.getY();
+			double len = Math.sqrt(vx * vx + vy * vy);
+
+			// JY addition.
+			int level1 =
+				minLevels.get(v1).intValue();
+			int level2 =
+				minLevels.get(v2).intValue();
+
+			double desiredLen = lengthFunction.apply(e);
+
+			// round from zero, if needed [zero would be Bad.].
+			len = (len == 0) ? .0001 : len;
+
+			// force factor: optimal length minus actual length,
+			// is made smaller as the current actual length gets larger.
+			// why?
+
+			// System.out.println("Desired : " + getLength( e ));
+			double f = force_multiplier * (desiredLen - len) / len;
+
+			f = f * Math.pow(stretch / 100.0,
+					(getGraph().degree(v1) + getGraph().degree(v2) -2));
+
+			// JY addition. If this is an edge which stretches a long way,
+			// don't be so concerned about it.
+			if (level1 != level2)
+				f = f / Math.pow(Math.abs(level2 - level1), 1.5);
+
+			// the actual movement distance 'dx' is the force multiplied by the
+			// distance to go.
+			double dx = f * vx;
+			double dy = f * vy;
+			SpringVertexData v1D, v2D;
+			v1D = springVertexData.getUnchecked(v1);
+			v2D = springVertexData.getUnchecked(v2);
+
+			v1D.edgedx += dx;
+			v1D.edgedy += dy;
+			v2D.edgedx += -dx;
+			v2D.edgedy += -dy;
+		}
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/FRLayout.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/FRLayout.java
new file mode 100644
index 0000000..e6ec131
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/FRLayout.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+import java.util.ConcurrentModificationException;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer;
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * Implements the Fruchterman-Reingold force-directed algorithm for node layout.
+ * 
+ * <p>Behavior is determined by the following settable parameters:
+ * <ul>
+ * <li>attraction multiplier: how much edges try to keep their vertices together
+ * <li>repulsion multiplier: how much vertices try to push each other apart
+ * <li>maximum iterations: how many iterations this algorithm will use before stopping
+ * </ul>
+ * Each of the first two defaults to 0.75; the maximum number of iterations defaults to 700.
+ *
+ * @see "Fruchterman and Reingold, 'Graph Drawing by Force-directed Placement'"
+ * @see "http://i11www.ilkd.uni-karlsruhe.de/teaching/SS_04/visualisierung/papers/fruchterman91graph.pdf"
+ * @author Scott White, Yan-Biao Boey, Danyel Fisher
+ */
+public class FRLayout<V, E> extends AbstractLayout<V, E> implements IterativeContext {
+
+    private double forceConstant;
+
+    private double temperature;
+
+    private int currentIteration;
+
+    private int mMaxIterations = 700;
+
+    protected LoadingCache<V, FRVertexData> frVertexData =
+    	CacheBuilder.newBuilder().build(new CacheLoader<V, FRVertexData>() {
+	    	public FRVertexData load(V vertex) {
+	    		return new FRVertexData();
+	    	}
+    });
+
+    private double attraction_multiplier = 0.75;
+
+    private double attraction_constant;
+
+    private double repulsion_multiplier = 0.75;
+
+    private double repulsion_constant;
+
+    private double max_dimension;
+
+    public FRLayout(Graph<V, E> g) {
+        super(g);
+    }
+
+    public FRLayout(Graph<V, E> g, Dimension d) {
+        super(g, new RandomLocationTransformer<V>(d), d);
+        initialize();
+        max_dimension = Math.max(d.height, d.width);
+    }
+
+	@Override
+	public void setSize(Dimension size) {
+		if(initialized == false) {
+			setInitializer(new RandomLocationTransformer<V>(size));
+		}
+		super.setSize(size);
+        max_dimension = Math.max(size.height, size.width);
+	}
+
+	public void setAttractionMultiplier(double attraction) {
+        this.attraction_multiplier = attraction;
+    }
+
+    public void setRepulsionMultiplier(double repulsion) {
+        this.repulsion_multiplier = repulsion;
+    }
+
+	public void reset() {
+		doInit();
+	}
+
+    public void initialize() {
+    	doInit();
+    }
+
+    private void doInit() {
+    	Graph<V,E> graph = getGraph();
+    	Dimension d = getSize();
+    	if(graph != null && d != null) {
+    		currentIteration = 0;
+    		temperature = d.getWidth() / 10;
+
+    		forceConstant =
+    			Math
+    			.sqrt(d.getHeight()
+    					* d.getWidth()
+    					/ graph.getVertexCount());
+
+    		attraction_constant = attraction_multiplier * forceConstant;
+    		repulsion_constant = repulsion_multiplier * forceConstant;
+    	}
+    }
+
+    private double EPSILON = 0.000001D;
+
+    /**
+     * Moves the iteration forward one notch, calculation attraction and
+     * repulsion between vertices and edges and cooling the temperature.
+     */
+    public synchronized void step() {
+        currentIteration++;
+
+        /**
+         * Calculate repulsion
+         */
+        while(true) {
+
+            try {
+                for(V v1 : getGraph().getVertices()) {
+                    calcRepulsion(v1);
+                }
+                break;
+            } catch(ConcurrentModificationException cme) {}
+        }
+
+        /**
+         * Calculate attraction
+         */
+        while(true) {
+            try {
+                for(E e : getGraph().getEdges()) {
+
+                    calcAttraction(e);
+                }
+                break;
+            } catch(ConcurrentModificationException cme) {}
+        }
+
+
+        while(true) {
+            try {
+                for(V v : getGraph().getVertices()) {
+                    if (isLocked(v)) continue;
+                    calcPositions(v);
+                }
+                break;
+            } catch(ConcurrentModificationException cme) {}
+        }
+        cool();
+    }
+
+    protected synchronized void calcPositions(V v) {
+        FRVertexData fvd = getFRData(v);
+        if(fvd == null) return;
+        Point2D xyd = apply(v);
+        double deltaLength = Math.max(EPSILON, fvd.norm());
+
+        double newXDisp = fvd.getX() / deltaLength
+                * Math.min(deltaLength, temperature);
+
+        if (Double.isNaN(newXDisp)) {
+        	throw new IllegalArgumentException(
+                "Unexpected mathematical result in FRLayout:calcPositions [xdisp]"); }
+
+        double newYDisp = fvd.getY() / deltaLength
+                * Math.min(deltaLength, temperature);
+        xyd.setLocation(xyd.getX()+newXDisp, xyd.getY()+newYDisp);
+
+        double borderWidth = getSize().getWidth() / 50.0;
+        double newXPos = xyd.getX();
+        if (newXPos < borderWidth) {
+            newXPos = borderWidth + Math.random() * borderWidth * 2.0;
+        } else if (newXPos > (getSize().getWidth() - borderWidth)) {
+            newXPos = getSize().getWidth() - borderWidth - Math.random()
+                    * borderWidth * 2.0;
+        }
+
+        double newYPos = xyd.getY();
+        if (newYPos < borderWidth) {
+            newYPos = borderWidth + Math.random() * borderWidth * 2.0;
+        } else if (newYPos > (getSize().getHeight() - borderWidth)) {
+            newYPos = getSize().getHeight() - borderWidth
+                    - Math.random() * borderWidth * 2.0;
+        }
+
+        xyd.setLocation(newXPos, newYPos);
+    }
+
+    protected void calcAttraction(E e) {
+    	Pair<V> endpoints = getGraph().getEndpoints(e);
+        V v1 = endpoints.getFirst();
+        V v2 = endpoints.getSecond();
+        boolean v1_locked = isLocked(v1);
+        boolean v2_locked = isLocked(v2);
+
+        if(v1_locked && v2_locked) {
+        	// both locked, do nothing
+        	return;
+        }
+        Point2D p1 = apply(v1);
+        Point2D p2 = apply(v2);
+        if(p1 == null || p2 == null) return;
+        double xDelta = p1.getX() - p2.getX();
+        double yDelta = p1.getY() - p2.getY();
+
+        double deltaLength = Math.max(EPSILON, Math.sqrt((xDelta * xDelta)
+                + (yDelta * yDelta)));
+
+        double force = (deltaLength * deltaLength) / attraction_constant;
+
+        if (Double.isNaN(force)) { throw new IllegalArgumentException(
+                "Unexpected mathematical result in FRLayout:calcPositions [force]"); }
+
+        double dx = (xDelta / deltaLength) * force;
+        double dy = (yDelta / deltaLength) * force;
+        if(v1_locked == false) {
+        	FRVertexData fvd1 = getFRData(v1);
+        	fvd1.offset(-dx, -dy);
+        }
+        if(v2_locked == false) {
+        	FRVertexData fvd2 = getFRData(v2);
+        	fvd2.offset(dx, dy);
+        }
+    }
+
+    protected void calcRepulsion(V v1) {
+        FRVertexData fvd1 = getFRData(v1);
+        if(fvd1 == null)
+            return;
+        fvd1.setLocation(0, 0);
+
+        try {
+            for(V v2 : getGraph().getVertices()) {
+
+//                if (isLocked(v2)) continue;
+                if (v1 != v2) {
+                    Point2D p1 = apply(v1);
+                    Point2D p2 = apply(v2);
+                    if(p1 == null || p2 == null) continue;
+                    double xDelta = p1.getX() - p2.getX();
+                    double yDelta = p1.getY() - p2.getY();
+
+                    double deltaLength = Math.max(EPSILON, Math
+                            .sqrt((xDelta * xDelta) + (yDelta * yDelta)));
+
+                    double force = (repulsion_constant * repulsion_constant) / deltaLength;
+
+                    if (Double.isNaN(force)) { throw new RuntimeException(
+                    "Unexpected mathematical result in FRLayout:calcPositions [repulsion]"); }
+
+                    fvd1.offset((xDelta / deltaLength) * force,
+                            (yDelta / deltaLength) * force);
+                }
+            }
+        } catch(ConcurrentModificationException cme) {
+            calcRepulsion(v1);
+        }
+    }
+
+    private void cool() {
+        temperature *= (1.0 - currentIteration / (double) mMaxIterations);
+    }
+
+    public void setMaxIterations(int maxIterations) {
+        mMaxIterations = maxIterations;
+    }
+
+    protected FRVertexData getFRData(V v) {
+        return frVertexData.getUnchecked(v);
+    }
+
+    /**
+     * @return true
+     */
+    public boolean isIncremental() {
+        return true;
+    }
+
+    /**
+     * @return true once the current iteration has passed the maximum count.
+     */
+    public boolean done() {
+        if (currentIteration > mMaxIterations || temperature < 1.0/max_dimension)
+        {
+            return true;
+        }
+        return false;
+    }
+
+    @SuppressWarnings("serial")
+	protected static class FRVertexData extends Point2D.Double
+    {
+        protected void offset(double x, double y)
+        {
+            this.x += x;
+            this.y += y;
+        }
+
+        protected double norm()
+        {
+            return Math.sqrt(x*x + y*y);
+        }
+     }
+}
\ No newline at end of file
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/FRLayout2.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/FRLayout2.java
new file mode 100644
index 0000000..4214e12
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/FRLayout2.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ConcurrentModificationException;
+
+import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer;
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * Implements the Fruchterman-Reingold force-directed algorithm for node layout.
+ * This is an experimental attempt at optimizing {@code FRLayout}; if it is successful
+ * it will be folded back into {@code FRLayout} (and this class will disappear).
+ * 
+ * <p>Behavior is determined by the following settable parameters:
+ * <ul>
+ * <li>attraction multiplier: how much edges try to keep their vertices together
+ * <li>repulsion multiplier: how much vertices try to push each other apart
+ * <li>maximum iterations: how many iterations this algorithm will use before stopping
+ * </ul>
+ * Each of the first two defaults to 0.75; the maximum number of iterations defaults to 700.
+
+ * 
+ * @see "Fruchterman and Reingold, 'Graph Drawing by Force-directed Placement'"
+ * @see "http://i11www.ilkd.uni-karlsruhe.de/teaching/SS_04/visualisierung/papers/fruchterman91graph.pdf"
+ * 
+ * @author Tom Nelson
+ * @author Scott White, Yan-Biao Boey, Danyel Fisher
+ */
+public class FRLayout2<V, E> extends AbstractLayout<V, E> implements IterativeContext {
+
+    private double forceConstant;
+
+    private double temperature;
+
+    private int currentIteration;
+
+    private int maxIterations = 700;
+    
+    protected LoadingCache<V, Point2D> frVertexData =
+    	CacheBuilder.newBuilder().build(new CacheLoader<V, Point2D>() {
+	    	public Point2D load(V vertex) {
+	    		return new Point2D.Double();
+	    	}
+    });
+
+    private double attraction_multiplier = 0.75;
+    
+    private double attraction_constant;
+    
+    private double repulsion_multiplier = 0.75;
+    
+    private double repulsion_constant;
+    
+    private double max_dimension;
+    
+    private Rectangle2D innerBounds = new Rectangle2D.Double();
+    
+    private boolean checked = false;
+    
+    public FRLayout2(Graph<V, E> g) {
+        super(g);
+    }
+    
+    public FRLayout2(Graph<V, E> g, Dimension d) {
+        super(g, new RandomLocationTransformer<V>(d), d);
+        max_dimension = Math.max(d.height, d.width);
+        initialize();
+    }
+    
+	@Override
+	public void setSize(Dimension size) {
+		if(initialized == false) 
+			setInitializer(new RandomLocationTransformer<V>(size));
+		super.setSize(size);
+		double t = size.width/50.0;
+		innerBounds.setFrameFromDiagonal(t,t,size.width-t,size.height-t);
+        max_dimension = Math.max(size.height, size.width);
+	}
+
+	public void setAttractionMultiplier(double attraction) {
+        this.attraction_multiplier = attraction;
+    }
+    
+    public void setRepulsionMultiplier(double repulsion) {
+        this.repulsion_multiplier = repulsion;
+    }
+    
+	public void reset() {
+		doInit();
+	}
+    
+    public void initialize() {
+    	doInit();
+    }
+
+    private void doInit() {
+    	Graph<V,E> graph = getGraph();
+    	Dimension d = getSize();
+    	if(graph != null && d != null) {
+    		currentIteration = 0;
+    		temperature = d.getWidth() / 10;
+
+    		forceConstant = 
+    			Math
+    			.sqrt(d.getHeight()
+    					* d.getWidth()
+    					/ graph.getVertexCount());
+
+    		attraction_constant = attraction_multiplier * forceConstant;
+    		repulsion_constant = repulsion_multiplier * forceConstant;
+    	}
+    }
+
+    private double EPSILON = 0.000001D;
+
+    /**
+     * Moves the iteration forward one notch, calculation attraction and
+     * repulsion between vertices and edges and cooling the temperature.
+     */
+    public synchronized void step() {
+        currentIteration++;
+
+        /**
+         * Calculate repulsion
+         */
+        while(true) {
+            
+            try {
+                for(V v1 : getGraph().getVertices()) {
+                    calcRepulsion(v1);
+                }
+                break;
+            } catch(ConcurrentModificationException cme) {}
+        }
+
+        /**
+         * Calculate attraction
+         */
+        while(true) {
+            try {
+                for(E e : getGraph().getEdges()) {
+                    calcAttraction(e);
+                }
+                break;
+            } catch(ConcurrentModificationException cme) {}
+        }
+
+
+        while(true) {
+            try {    
+                for(V v : getGraph().getVertices()) {
+                    if (isLocked(v)) continue;
+                    calcPositions(v);
+                }
+                break;
+            } catch(ConcurrentModificationException cme) {}
+        }
+        cool();
+    }
+
+    protected synchronized void calcPositions(V v) {
+        Point2D fvd = this.frVertexData.getUnchecked(v);
+        if(fvd == null) return;
+        Point2D xyd = apply(v);
+        double deltaLength = Math.max(EPSILON, 
+        		Math.sqrt(fvd.getX()*fvd.getX()+fvd.getY()*fvd.getY()));
+
+        double newXDisp = fvd.getX() / deltaLength
+                * Math.min(deltaLength, temperature);
+
+        Preconditions.checkState(!Double.isNaN(newXDisp),
+            "Unexpected mathematical result in FRLayout:calcPositions [xdisp]");
+
+        double newYDisp = fvd.getY() / deltaLength
+                * Math.min(deltaLength, temperature);
+        double newX = xyd.getX()+Math.max(-5, Math.min(5,newXDisp));
+        double newY = xyd.getY()+Math.max(-5, Math.min(5,newYDisp));
+        
+        newX = Math.max(innerBounds.getMinX(), Math.min(newX, innerBounds.getMaxX()));
+        newY = Math.max(innerBounds.getMinY(), Math.min(newY, innerBounds.getMaxY()));
+        
+        xyd.setLocation(newX, newY);
+
+    }
+
+    protected void calcAttraction(E e) {
+    	Pair<V> endpoints = getGraph().getEndpoints(e);
+        V v1 = endpoints.getFirst();
+        V v2 = endpoints.getSecond();
+        boolean v1_locked = isLocked(v1);
+        boolean v2_locked = isLocked(v2);
+        
+        if(v1_locked && v2_locked) {
+        	// both locked, do nothing
+        	return;
+        }
+        Point2D p1 = apply(v1);
+        Point2D p2 = apply(v2);
+        if(p1 == null || p2 == null) return;
+        double xDelta = p1.getX() - p2.getX();
+        double yDelta = p1.getY() - p2.getY();
+
+        double deltaLength = Math.max(EPSILON, p1.distance(p2));
+
+        double force = deltaLength  / attraction_constant;
+
+        Preconditions.checkState(!Double.isNaN(force),
+            "Unexpected mathematical result in FRLayout:calcPositions [force]");
+
+        double dx = xDelta * force;
+        double dy = yDelta * force;
+        Point2D fvd1 = frVertexData.getUnchecked(v1);
+        Point2D fvd2 = frVertexData.getUnchecked(v2);
+        if(v2_locked) {
+        	// double the offset for v1, as v2 will not be moving in
+        	// the opposite direction
+        	fvd1.setLocation(fvd1.getX()-2*dx, fvd1.getY()-2*dy);
+        } else {
+        fvd1.setLocation(fvd1.getX()-dx, fvd1.getY()-dy);
+        }
+        if(v1_locked) {
+        	// double the offset for v2, as v1 will not be moving in
+        	// the opposite direction
+        	fvd2.setLocation(fvd2.getX()+2*dx, fvd2.getY()+2*dy);
+        } else {
+        fvd2.setLocation(fvd2.getX()+dx, fvd2.getY()+dy);
+    }
+    }
+
+    protected void calcRepulsion(V v1) {
+        Point2D fvd1 = frVertexData.getUnchecked(v1);
+        if(fvd1 == null) return;
+        fvd1.setLocation(0, 0);
+        boolean v1_locked = isLocked(v1);
+
+        try {
+            for(V v2 : getGraph().getVertices()) {
+
+                boolean v2_locked = isLocked(v2);
+            	if (v1_locked && v2_locked) continue;
+                if (v1 != v2) {
+                    Point2D p1 = apply(v1);
+                    Point2D p2 = apply(v2);
+                    if(p1 == null || p2 == null) continue;
+                    double xDelta = p1.getX() - p2.getX();
+                    double yDelta = p1.getY() - p2.getY();
+                    
+                    double deltaLength = Math.max(EPSILON, p1.distanceSq(p2));
+                    
+                    double force = (repulsion_constant * repulsion_constant);// / deltaLength;
+                    
+                    double forceOverDeltaLength = force / deltaLength;
+                    
+                    Preconditions.checkState(!Double.isNaN(force),
+                        "Unexpected mathematical result in FRLayout:calcPositions [repulsion]");
+                    
+                    if(v2_locked) {
+                    	// double the offset for v1, as v2 will not be moving in
+                    	// the opposite direction
+                    	fvd1.setLocation(fvd1.getX()+2 * xDelta * forceOverDeltaLength,
+                    		fvd1.getY()+ 2 * yDelta * forceOverDeltaLength);
+                    } else {
+                    	fvd1.setLocation(fvd1.getX()+xDelta * forceOverDeltaLength,
+                        		fvd1.getY()+yDelta * forceOverDeltaLength);
+                }
+            }
+            }
+        } catch(ConcurrentModificationException cme) {
+            calcRepulsion(v1);
+        }
+    }
+
+    private void cool() {
+        temperature *= (1.0 - currentIteration / (double) maxIterations);
+    }
+
+    public void setMaxIterations(int maxIterations) {
+        this.maxIterations = maxIterations;
+    }
+
+    /**
+     * @return true
+     */
+    public boolean isIncremental() {
+        return true;
+    }
+
+    /**
+     * @return true once the current iteration has passed the maximum count.
+     */
+    public boolean done() {
+        if (currentIteration > maxIterations || temperature < 1.0/max_dimension) { 
+            if (!checked)
+            {
+                checked = true;
+            }
+            return true; 
+        } 
+        return false;
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/GraphElementAccessor.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/GraphElementAccessor.java
new file mode 100644
index 0000000..cfce5ee
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/GraphElementAccessor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ *
+ * Created on Apr 12, 2005
+ */
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Shape;
+import java.util.Collection;
+
+/**
+ * Interface for coordinate-based selection of graph components.
+ * @author Tom Nelson
+ * @author Joshua O'Madadhain
+ */
+public interface GraphElementAccessor<V, E>
+{
+	/** 
+     * Returns the vertex, if any, associated with (x, y).
+     * 
+     * @param layout the layout instance that records the positions for all vertices
+     * @param x the x coordinate of the pick point
+     * @param y the y coordinate of the pick point
+     * @return the vertex associated with (x, y)
+     */
+    V getVertex(Layout<V,E> layout, double x, double y);
+    
+    /**
+     * @param layout the layout instance that records the positions for all vertices
+     * @param rectangle the region in which the returned vertices are located
+     * @return the vertices whose locations given by {@code layout}
+     *     are contained within {@code rectangle}
+     */
+    Collection<V> getVertices(Layout<V,E> layout, Shape rectangle);
+
+    /**
+	 * @param layout the context in which the location is defined
+	 * @param x the x coordinate of the location
+	 * @param y the y coordinate of the location
+     * @return an edge which is associated with the location {@code (x,y)}
+     *     as given by {@code layout}, generally by reference to the edge's endpoints
+     */
+    E getEdge(Layout<V,E> layout, double x, double y);
+}
\ No newline at end of file
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/ISOMLayout.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/ISOMLayout.java
new file mode 100644
index 0000000..22ad2b8
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/ISOMLayout.java
@@ -0,0 +1,225 @@
+/*
+* Copyright (c) 2003, The JUNG Authors
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.List;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer;
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * Implements a self-organizing map layout algorithm, based on Meyer's
+ * self-organizing graph methods.
+ *
+ * @author Yan Biao Boey
+ */
+public class ISOMLayout<V, E> extends AbstractLayout<V,E> implements IterativeContext {
+
+    protected LoadingCache<V, ISOMVertexData> isomVertexData =
+    	CacheBuilder.newBuilder().build(new CacheLoader<V, ISOMVertexData>() {
+	    	public ISOMVertexData load(V vertex) {
+	    		return new ISOMVertexData();
+	    	}
+    });
+
+	private int maxEpoch;
+	private int epoch;
+
+	private int radiusConstantTime;
+	private int radius;
+	private int minRadius;
+
+	private double adaption;
+	private double initialAdaption;
+	private double minAdaption;
+
+    protected GraphElementAccessor<V,E> elementAccessor =
+    	new RadiusGraphElementAccessor<V,E>();
+
+	private double coolingFactor;
+
+	private List<V> queue = new ArrayList<V>();
+	private String status = null;
+
+	/**
+	 * @return the current number of epochs and execution status, as a string.
+	 */
+	public String getStatus() {
+		return status;
+	}
+
+	public ISOMLayout(Graph<V,E> g) {
+		super(g);
+	}
+
+	public void initialize() {
+
+		setInitializer(new RandomLocationTransformer<V>(getSize()));
+		maxEpoch = 2000;
+		epoch = 1;
+
+		radiusConstantTime = 100;
+		radius = 5;
+		minRadius = 1;
+
+		initialAdaption = 90.0D / 100.0D;
+		adaption = initialAdaption;
+		minAdaption = 0;
+
+		//factor = 0; //Will be set later on
+		coolingFactor = 2;
+
+		//temperature = 0.03;
+		//initialJumpRadius = 100;
+		//jumpRadius = initialJumpRadius;
+
+		//delay = 100;
+	}
+
+
+	/**
+	* Advances the current positions of the graph elements.
+	*/
+	public void step() {
+		status = "epoch: " + epoch + "; ";
+		if (epoch < maxEpoch) {
+			adjust();
+			updateParameters();
+			status += " status: running";
+		} else {
+			status += "adaption: " + adaption + "; ";
+			status += "status: done";
+//			done = true;
+		}
+	}
+
+	private synchronized void adjust() {
+		//Generate random position in graph space
+		Point2D tempXYD = new Point2D.Double();
+
+		// creates a new XY data location
+        tempXYD.setLocation(10 + Math.random() * getSize().getWidth(),
+                10 + Math.random() * getSize().getHeight());
+
+		//Get closest vertex to random position
+		V winner = elementAccessor.getVertex(this, tempXYD.getX(), tempXYD.getY());
+
+		while(true) {
+		    try {
+		    	for(V v : getGraph().getVertices()) {
+		            ISOMVertexData ivd = getISOMVertexData(v);
+		            ivd.distance = 0;
+		            ivd.visited = false;
+		        }
+		        break;
+		    } catch(ConcurrentModificationException cme) {}
+        }
+		adjustVertex(winner, tempXYD);
+	}
+
+	private synchronized void updateParameters() {
+		epoch++;
+		double factor = Math.exp(-1 * coolingFactor * (1.0 * epoch / maxEpoch));
+		adaption = Math.max(minAdaption, factor * initialAdaption);
+		//jumpRadius = (int) factor * jumpRadius;
+		//temperature = factor * temperature;
+		if ((radius > minRadius) && (epoch % radiusConstantTime == 0)) {
+			radius--;
+		}
+	}
+
+	private synchronized void adjustVertex(V v, Point2D tempXYD) {
+		queue.clear();
+		ISOMVertexData ivd = getISOMVertexData(v);
+		ivd.distance = 0;
+		ivd.visited = true;
+		queue.add(v);
+		V current;
+
+		while (!queue.isEmpty()) {
+			current = queue.remove(0);
+			ISOMVertexData currData = getISOMVertexData(current);
+			Point2D currXYData = apply(current);
+
+			double dx = tempXYD.getX() - currXYData.getX();
+			double dy = tempXYD.getY() - currXYData.getY();
+			double factor = adaption / Math.pow(2, currData.distance);
+
+			currXYData.setLocation(currXYData.getX()+(factor*dx), currXYData.getY()+(factor*dy));
+
+			if (currData.distance < radius) {
+			    Collection<V> s = getGraph().getNeighbors(current);
+			    while(true) {
+			        try {
+			        	for(V child : s) {
+			                ISOMVertexData childData = getISOMVertexData(child);
+			                if (childData != null && !childData.visited) {
+			                    childData.visited = true;
+			                    childData.distance = currData.distance + 1;
+			                    queue.add(child);
+			                }
+			            }
+			            break;
+			        } catch(ConcurrentModificationException cme) {}
+			    }
+			}
+		}
+	}
+
+	protected ISOMVertexData getISOMVertexData(V v) {
+		return isomVertexData.getUnchecked(v);
+	}
+
+	/**
+	 * This one is an incremental visualization.
+	 * @return <code>true</code> is the layout algorithm is incremental, <code>false</code> otherwise
+	 */
+	public boolean isIncremental() {
+		return true;
+	}
+
+	/**
+	 * Returns <code>true</code> if the vertex positions are no longer being
+	 * updated.  Currently <code>ISOMLayout</code> stops updating vertex
+	 * positions after a certain number of iterations have taken place.
+	 * @return <code>true</code> if the vertex position updates have stopped,
+	 * <code>false</code> otherwise
+	 */
+	public boolean done() {
+		return epoch >= maxEpoch;
+	}
+
+	protected static class ISOMVertexData {
+		int distance;
+		boolean visited;
+
+		protected ISOMVertexData() {
+		    distance = 0;
+		    visited = false;
+		}
+	}
+
+	/**
+	 * Resets the layout iteration count to 0, which allows the layout algorithm to
+	 * continue updating vertex positions.
+	 */
+	public void reset() {
+		epoch = 0;
+	}
+}
\ No newline at end of file
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/KKLayout.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/KKLayout.java
new file mode 100644
index 0000000..dcac0f7
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/KKLayout.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.layout;
+/*
+ * This source is under the same license with JUNG.
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+import java.util.ConcurrentModificationException;
+
+import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer;
+import edu.uci.ics.jung.algorithms.shortestpath.Distance;
+import edu.uci.ics.jung.algorithms.shortestpath.DistanceStatistics;
+import edu.uci.ics.jung.algorithms.shortestpath.UnweightedShortestPath;
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * Implements the Kamada-Kawai algorithm for node layout.
+ * Does not respect filter calls, and sometimes crashes when the view changes to it.
+ *
+ * @see "Tomihisa Kamada and Satoru Kawai: An algorithm for drawing general indirect graphs. Information Processing Letters 31(1):7-15, 1989" 
+ * @see "Tomihisa Kamada: On visualization of abstract objects and relations. Ph.D. dissertation, Dept. of Information Science, Univ. of Tokyo, Dec. 1988."
+ *
+ * @author Masanori Harada
+ */
+public class KKLayout<V,E> extends AbstractLayout<V,E> implements IterativeContext {
+
+	private double EPSILON = 0.1d;
+
+	private int currentIteration;
+    private int maxIterations = 2000;
+	private String status = "KKLayout";
+
+	private double L;			// the ideal length of an edge
+	private double K = 1;		// arbitrary const number
+	private double[][] dm;     // distance matrix
+
+	private boolean adjustForGravity = true;
+	private boolean exchangeVertices = true;
+
+	private V[] vertices;
+	private Point2D[] xydata;
+
+    /**
+     * Retrieves graph distances between vertices of the visible graph
+     */
+    protected Distance<V> distance;
+
+    /**
+     * The diameter of the visible graph. In other words, the maximum over all pairs
+     * of vertices of the length of the shortest path between a and bf the visible graph.
+     */
+	protected double diameter;
+
+    /**
+     * A multiplicative factor which partly specifies the "preferred" length of an edge (L).
+     */
+    private double length_factor = 0.9;
+
+    /**
+     * A multiplicative factor which specifies the fraction of the graph's diameter to be 
+     * used as the inter-vertex distance between disconnected vertices.
+     */
+    private double disconnected_multiplier = 0.5;
+    
+	public KKLayout(Graph<V,E> g) 
+    {
+        this(g, new UnweightedShortestPath<V,E>(g));
+	}
+
+	/**
+	 * Creates an instance for the specified graph and distance metric.
+	 * @param g the graph on which the layout algorithm is to operate
+	 * @param distance specifies the distance between pairs of vertices
+	 */
+    public KKLayout(Graph<V,E> g, Distance<V> distance){
+        super(g);
+        this.distance = distance;
+    }
+
+    /**
+     * @param length_factor a multiplicative factor which partially specifies
+     *     the preferred length of an edge
+     */
+    public void setLengthFactor(double length_factor){
+        this.length_factor = length_factor;
+    }
+    
+    /**
+     * @param disconnected_multiplier a multiplicative factor that specifies the fraction of the
+     *     graph's diameter to be used as the inter-vertex distance between disconnected vertices
+     */
+    public void setDisconnectedDistanceMultiplier(double disconnected_multiplier){
+        this.disconnected_multiplier = disconnected_multiplier;
+    }
+    
+    /**
+     * @return a string with information about the current status of the algorithm.
+     */
+	public String getStatus() {
+		return status + this.getSize();
+	}
+
+    public void setMaxIterations(int maxIterations) {
+        this.maxIterations = maxIterations;
+    }
+
+	/**
+	 * @return true
+	 */
+	public boolean isIncremental() {
+		return true;
+	}
+
+	/**
+	 * @return true if the current iteration has passed the maximum count.
+	 */
+	public boolean done() {
+		if (currentIteration > maxIterations) {
+			return true;
+		}
+		return false;
+	}
+
+	@SuppressWarnings("unchecked")
+    public void initialize() {
+    	currentIteration = 0;
+
+    	if(graph != null && size != null) {
+
+        	double height = size.getHeight();
+    		double width = size.getWidth();
+
+    		int n = graph.getVertexCount();
+    		dm = new double[n][n];
+    		vertices = (V[])graph.getVertices().toArray();
+    		xydata = new Point2D[n];
+
+    		// assign IDs to all visible vertices
+    		while(true) {
+    			try {
+    				int index = 0;
+    				for(V v : graph.getVertices()) {
+    					Point2D xyd = apply(v);
+    					vertices[index] = v;
+    					xydata[index] = xyd;
+    					index++;
+    				}
+    				break;
+    			} catch(ConcurrentModificationException cme) {}
+    		}
+
+    		diameter = DistanceStatistics.<V,E>diameter(graph, distance, true);
+
+    		double L0 = Math.min(height, width);
+    		L = (L0 / diameter) * length_factor;  // length_factor used to be hardcoded to 0.9
+    		//L = 0.75 * Math.sqrt(height * width / n);
+
+    		for (int i = 0; i < n - 1; i++) {
+    			for (int j = i + 1; j < n; j++) {
+    				Number d_ij = distance.getDistance(vertices[i], vertices[j]);
+    				Number d_ji = distance.getDistance(vertices[j], vertices[i]);
+    				double dist = diameter * disconnected_multiplier;
+    				if (d_ij != null)
+    					dist = Math.min(d_ij.doubleValue(), dist);
+    				if (d_ji != null)
+    					dist = Math.min(d_ji.doubleValue(), dist);
+    				dm[i][j] = dm[j][i] = dist;
+    			}
+    		}
+    	}
+	}
+
+	public void step() {
+		try {
+			currentIteration++;
+			double energy = calcEnergy();
+			status = "Kamada-Kawai V=" + getGraph().getVertexCount()
+			+ "(" + getGraph().getVertexCount() + ")"
+			+ " IT: " + currentIteration
+			+ " E=" + energy
+			;
+
+			int n = getGraph().getVertexCount();
+			if (n == 0)
+				return;
+
+			double maxDeltaM = 0;
+			int pm = -1;            // the node having max deltaM
+			for (int i = 0; i < n; i++) {
+				if (isLocked(vertices[i]))
+					continue;
+				double deltam = calcDeltaM(i);
+
+				if (maxDeltaM < deltam) {
+					maxDeltaM = deltam;
+					pm = i;
+				}
+			}
+			if (pm == -1)
+				return;
+
+			for (int i = 0; i < 100; i++) {
+				double[] dxy = calcDeltaXY(pm);
+				xydata[pm].setLocation(xydata[pm].getX()+dxy[0], xydata[pm].getY()+dxy[1]);
+
+				double deltam = calcDeltaM(pm);
+				if (deltam < EPSILON)
+					break;
+			}
+
+			if (adjustForGravity)
+				adjustForGravity();
+
+			if (exchangeVertices && maxDeltaM < EPSILON) {
+				energy = calcEnergy();
+				for (int i = 0; i < n - 1; i++) {
+					if (isLocked(vertices[i]))
+						continue;
+					for (int j = i + 1; j < n; j++) {
+						if (isLocked(vertices[j]))
+							continue;
+						double xenergy = calcEnergyIfExchanged(i, j);
+						if (energy > xenergy) {
+							double sx = xydata[i].getX();
+							double sy = xydata[i].getY();
+							xydata[i].setLocation(xydata[j]);
+							xydata[j].setLocation(sx, sy);
+							return;
+						}
+					}
+				}
+			}
+		}
+		finally {
+//			fireStateChanged();
+		}
+	}
+
+	/**
+	 * Shift all vertices so that the center of gravity is located at
+	 * the center of the screen.
+	 */
+	public void adjustForGravity() {
+		Dimension d = getSize();
+		double height = d.getHeight();
+		double width = d.getWidth();
+		double gx = 0;
+		double gy = 0;
+		for (int i = 0; i < xydata.length; i++) {
+			gx += xydata[i].getX();
+			gy += xydata[i].getY();
+		}
+		gx /= xydata.length;
+		gy /= xydata.length;
+		double diffx = width / 2 - gx;
+		double diffy = height / 2 - gy;
+		for (int i = 0; i < xydata.length; i++) {
+            xydata[i].setLocation(xydata[i].getX()+diffx, xydata[i].getY()+diffy);
+		}
+	}
+
+	@Override
+	public void setSize(Dimension size) {
+		if(initialized == false)
+			setInitializer(new RandomLocationTransformer<V>(size));
+		super.setSize(size);
+	}
+
+	public void setAdjustForGravity(boolean on) {
+		adjustForGravity = on;
+	}
+
+	public boolean getAdjustForGravity() {
+		return adjustForGravity;
+	}
+
+	/**
+	 * Enable or disable the local minimum escape technique by
+	 * exchanging vertices.
+	 * @param on iff the local minimum escape technique is to be enabled
+	 */
+	public void setExchangeVertices(boolean on) {
+		exchangeVertices = on;
+	}
+
+	public boolean getExchangeVertices() {
+		return exchangeVertices;
+	}
+
+	/**
+	 * Determines a step to new position of the vertex m.
+	 */
+	private double[] calcDeltaXY(int m) {
+		double dE_dxm = 0;
+		double dE_dym = 0;
+		double d2E_d2xm = 0;
+		double d2E_dxmdym = 0;
+		double d2E_dymdxm = 0;
+		double d2E_d2ym = 0;
+
+		for (int i = 0; i < vertices.length; i++) {
+			if (i != m) {
+                
+                double dist = dm[m][i];
+				double l_mi = L * dist;
+				double k_mi = K / (dist * dist);
+				double dx = xydata[m].getX() - xydata[i].getX();
+				double dy = xydata[m].getY() - xydata[i].getY();
+				double d = Math.sqrt(dx * dx + dy * dy);
+				double ddd = d * d * d;
+
+				dE_dxm += k_mi * (1 - l_mi / d) * dx;
+				dE_dym += k_mi * (1 - l_mi / d) * dy;
+				d2E_d2xm += k_mi * (1 - l_mi * dy * dy / ddd);
+				d2E_dxmdym += k_mi * l_mi * dx * dy / ddd;
+				d2E_d2ym += k_mi * (1 - l_mi * dx * dx / ddd);
+			}
+		}
+		// d2E_dymdxm equals to d2E_dxmdym.
+		d2E_dymdxm = d2E_dxmdym;
+
+		double denomi = d2E_d2xm * d2E_d2ym - d2E_dxmdym * d2E_dymdxm;
+		double deltaX = (d2E_dxmdym * dE_dym - d2E_d2ym * dE_dxm) / denomi;
+		double deltaY = (d2E_dymdxm * dE_dxm - d2E_d2xm * dE_dym) / denomi;
+		return new double[]{deltaX, deltaY};
+	}
+
+	/**
+	 * Calculates the gradient of energy function at the vertex m.
+	 */
+	private double calcDeltaM(int m) {
+		double dEdxm = 0;
+		double dEdym = 0;
+		for (int i = 0; i < vertices.length; i++) {
+			if (i != m) {
+                double dist = dm[m][i];
+				double l_mi = L * dist;
+				double k_mi = K / (dist * dist);
+
+				double dx = xydata[m].getX() - xydata[i].getX();
+				double dy = xydata[m].getY() - xydata[i].getY();
+				double d = Math.sqrt(dx * dx + dy * dy);
+
+				double common = k_mi * (1 - l_mi / d);
+				dEdxm += common * dx;
+				dEdym += common * dy;
+			}
+		}
+		return Math.sqrt(dEdxm * dEdxm + dEdym * dEdym);
+	}
+
+	/**
+	 * Calculates the energy function E.
+	 */
+	private double calcEnergy() {
+		double energy = 0;
+		for (int i = 0; i < vertices.length - 1; i++) {
+			for (int j = i + 1; j < vertices.length; j++) {
+                double dist = dm[i][j];
+				double l_ij = L * dist;
+				double k_ij = K / (dist * dist);
+				double dx = xydata[i].getX() - xydata[j].getX();
+				double dy = xydata[i].getY() - xydata[j].getY();
+				double d = Math.sqrt(dx * dx + dy * dy);
+
+
+				energy += k_ij / 2 * (dx * dx + dy * dy + l_ij * l_ij -
+									  2 * l_ij * d);
+			}
+		}
+		return energy;
+	}
+
+	/**
+	 * Calculates the energy function E as if positions of the
+	 * specified vertices are exchanged.
+	 */
+	private double calcEnergyIfExchanged(int p, int q) {
+		if (p >= q)
+			throw new RuntimeException("p should be < q");
+		double energy = 0;		// < 0
+		for (int i = 0; i < vertices.length - 1; i++) {
+			for (int j = i + 1; j < vertices.length; j++) {
+				int ii = i;
+				int jj = j;
+				if (i == p) ii = q;
+				if (j == q) jj = p;
+
+                double dist = dm[i][j];
+				double l_ij = L * dist;
+				double k_ij = K / (dist * dist);
+				double dx = xydata[ii].getX() - xydata[jj].getX();
+				double dy = xydata[ii].getY() - xydata[jj].getY();
+				double d = Math.sqrt(dx * dx + dy * dy);
+				
+				energy += k_ij / 2 * (dx * dx + dy * dy + l_ij * l_ij -
+									  2 * l_ij * d);
+			}
+		}
+		return energy;
+	}
+
+	public void reset() {
+		currentIteration = 0;
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/Layout.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/Layout.java
new file mode 100644
index 0000000..36ed57e
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/Layout.java
@@ -0,0 +1,87 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * A generalized interface is a mechanism for returning (x,y) coordinates 
+ * from vertices. In general, most of these methods are used to both control and
+ * get information from the layout algorithm.
+ * <p>
+ * @author danyelf
+ * @author tom nelson
+ */
+public interface Layout<V, E> extends Function<V,Point2D> {
+    
+	/**
+	 * Initializes fields in the node that may not have
+	 * been set during the constructor. Must be called before
+	 * the iterations begin.
+	 */
+	void initialize();
+	
+	/**
+	 * @param initializer a function that specifies initial locations for all vertices
+	 */
+	void setInitializer(Function<V,Point2D> initializer);
+    
+	/**
+	 * @param graph the graph that this algorithm is to operate on
+	 */
+    void setGraph(Graph<V,E> graph);
+
+	/**
+	 * @return the graph that this Layout refers to
+	 */
+	Graph<V,E> getGraph();
+	
+	void reset();
+	
+	/**
+	 * @param d the space to use to lay out this graph
+	 */
+	void setSize(Dimension d);
+	
+	/**
+	 * @return the current size of the visualization's space
+	 */
+	Dimension getSize();
+
+
+	/**
+	 * Locks or unlocks the specified vertex.  Locking the vertex fixes it at its current position,
+	 * so that it will not be affected by the layout algorithm.  Unlocking it allows the layout
+	 * algorithm to change the vertex's position.
+     * 
+	 * @param v	the vertex to lock/unlock
+	 * @param state {@code true} to lock the vertex, {@code false} to unlock it
+	 */
+	void lock(V v, boolean state);
+
+    /**
+     * @param v the vertex whose locked state is being queried
+     * @return <code>true</code> if the position of vertex <code>v</code> is locked
+     */
+    boolean isLocked(V v);
+
+    /**
+     * Changes the layout coordinates of {@code v} to {@code location}.
+     * @param v the vertex whose location is to be specified
+     * @param location the coordinates of the specified location
+     */
+	void setLocation(V v, Point2D location);
+	
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/LayoutDecorator.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/LayoutDecorator.java
new file mode 100644
index 0000000..921ec99
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/LayoutDecorator.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * a pure decorator for the Layout interface. Intended to be overridden
+ * to provide specific behavior decoration
+ * 
+ * @author Tom Nelson 
+ *
+ */
+public abstract class LayoutDecorator<V, E> implements Layout<V, E>, IterativeContext {
+    
+    protected Layout<V, E> delegate;
+    
+	/**
+	 * Creates an instance backed by the specified {@code delegate}.
+	 * @param delegate the layout to which this instance is delegating
+	 */
+    public LayoutDecorator(Layout<V, E> delegate) {
+        this.delegate = delegate;
+    }
+
+    /**
+     * @return the backing (delegate) layout.
+     */
+    public Layout<V,E> getDelegate() {
+        return delegate;
+    }
+
+    public void setDelegate(Layout<V,E> delegate) {
+        this.delegate = delegate;
+    }
+
+    public void step() {
+    	if(delegate instanceof IterativeContext) {
+    		((IterativeContext)delegate).step();
+    	}
+    }
+
+	public void initialize() {
+		delegate.initialize();
+	}
+
+	public void setInitializer(Function<V, Point2D> initializer) {
+		delegate.setInitializer(initializer);
+	}
+
+	public void setLocation(V v, Point2D location) {
+		delegate.setLocation(v, location);
+	}
+
+    public Dimension getSize() {
+        return delegate.getSize();
+    }
+
+    public Graph<V, E> getGraph() {
+        return delegate.getGraph();
+    }
+
+    public Point2D transform(V v) {
+        return delegate.apply(v);
+    }
+
+    public boolean done() {
+    	if(delegate instanceof IterativeContext) {
+    		return ((IterativeContext)delegate).done();
+    	}
+    	return true;
+    }
+
+    public void lock(V v, boolean state) {
+        delegate.lock(v, state);
+    }
+
+    public boolean isLocked(V v) {
+        return delegate.isLocked(v);
+    }
+    
+    public void setSize(Dimension d) {
+        delegate.setSize(d);
+    }
+
+    public void reset() {
+    	delegate.reset();
+    }
+    
+    public void setGraph(Graph<V, E> graph) {
+        delegate.setGraph(graph);
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/PolarPoint.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/PolarPoint.java
new file mode 100644
index 0000000..bd7d1b6
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/PolarPoint.java
@@ -0,0 +1,106 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.geom.Point2D;
+
+/**
+ * Represents a point in polar coordinates: distance and angle from the origin.
+ * Includes conversions between polar and Cartesian
+ * coordinates (Point2D).
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ */
+public class PolarPoint 
+{
+	double theta;
+	double radius;
+	
+	/**
+	 * Creates a new instance with radius and angle each 0.
+	 */
+	public PolarPoint() {
+		this(0,0);
+	}
+
+	/**
+	 * Creates a new instance with the specified radius and angle.
+	 * @param theta the angle of the point to create
+	 * @param radius the distance from the origin of the point to create
+	 */
+	public PolarPoint(double theta, double radius) {
+		this.theta = theta;
+		this.radius = radius;
+	}
+	
+	/**
+	 * @return the angle for this point
+	 */
+	public double getTheta() { return theta; }
+
+	/**
+	 * @return the radius for this point
+	 */
+	public double getRadius() { return radius; }
+	
+	public void setTheta(double theta) { this.theta = theta; }
+	
+	public void setRadius(double radius) { this.radius = radius; }
+
+	/**
+	 * @param polar the input location to convert
+	 * @return the result of converting <code>polar</code> to Cartesian coordinates.
+	 */
+	public static Point2D polarToCartesian(PolarPoint polar) {
+		return polarToCartesian(polar.getTheta(), polar.getRadius());
+	}
+
+	/**
+	 * @param theta the angle of the input location
+	 * @param radius the distance from the origin of the input location
+	 * @return the result of converting <code>(theta, radius)</code> to Cartesian coordinates.
+	 */
+	public static Point2D polarToCartesian(double theta, double radius) {
+		return new Point2D.Double(radius*Math.cos(theta), radius*Math.sin(theta));
+	}
+
+	/**
+	 * @param point the input location
+	 * @return the result of converting <code>point</code> to polar coordinates.
+	 */
+	public static PolarPoint cartesianToPolar(Point2D point) {
+		return cartesianToPolar(point.getX(), point.getY());
+	}
+
+	/**
+	 * @param x the x coordinate of the input location
+	 * @param y the y coordinate of the input location
+	 * @return the result of converting <code>(x, y)</code> to polar coordinates.
+	 */
+	public static PolarPoint cartesianToPolar(double x, double y) {
+		double theta = Math.atan2(y,x);
+		double radius = Math.sqrt(x*x+y*y);
+		return new PolarPoint(theta, radius);
+	}
+	
+	@Override
+	public String toString() {
+	    return "PolarPoint[" + radius + "," + theta +"]";
+	}
+	
+	/**
+	 * Sets the angle and radius of this point to those of {@code p}.
+	 * @param p the point whose location is copied into this instance
+	 */
+	public void setLocation(PolarPoint p) {
+		this.theta = p.getTheta();
+		this.radius = p.getRadius();
+	}
+}
\ No newline at end of file
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/RadialTreeLayout.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/RadialTreeLayout.java
new file mode 100644
index 0000000..5be2fe2
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/RadialTreeLayout.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 9, 2005
+ */
+
+package edu.uci.ics.jung.algorithms.layout;
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+import java.util.HashMap;
+import java.util.Map;
+
+import edu.uci.ics.jung.graph.Forest;
+
+/**
+ * A radial layout for Tree or Forest graphs.
+ * 
+ * @author Tom Nelson 
+ *  
+ */
+public class RadialTreeLayout<V,E> extends TreeLayout<V,E> {
+
+    protected Map<V,PolarPoint> polarLocations;
+
+    public RadialTreeLayout(Forest<V,E> g) {
+    	this(g, DEFAULT_DISTX, DEFAULT_DISTY);
+    }
+
+    public RadialTreeLayout(Forest<V,E> g, int distx) {
+        this(g, distx, DEFAULT_DISTY);
+    }
+
+    public RadialTreeLayout(Forest<V,E> g, int distx, int disty) {
+    	super(g, distx, disty);
+    }
+    
+	@Override
+    protected void buildTree() {
+	    super.buildTree();
+	    this.polarLocations = new HashMap<V, PolarPoint>();
+        setRadialLocations();
+    }
+
+    @Override
+    public void setSize(Dimension size) {
+    	this.size = size;
+        buildTree();
+    }
+
+    @Override
+    protected void setCurrentPositionFor(V vertex) {
+    	locations.getUnchecked(vertex).setLocation(m_currentPoint);
+    }
+
+	@Override
+    public void setLocation(V v, Point2D location)
+    {
+        Point2D c = getCenter();
+        Point2D pv = new Point2D.Double(location.getX() - c.getX(), 
+                location.getY() - c.getY());
+        PolarPoint newLocation = PolarPoint.cartesianToPolar(pv);
+        PolarPoint currentLocation = polarLocations.get(v);
+        if (currentLocation == null)
+        	polarLocations.put(v, newLocation);
+        else
+        	currentLocation.setLocation(newLocation);
+     }
+	
+	/**
+	 * @return a map from vertices to their locations in polar coordinates.
+	 */
+	public Map<V,PolarPoint> getPolarLocations() {
+		return polarLocations;
+	}
+
+	@Override
+    public Point2D apply(V v) {
+		PolarPoint pp = polarLocations.get(v);
+		double centerX = getSize().getWidth()/2;
+		double centerY = getSize().getHeight()/2;
+		Point2D cartesian = PolarPoint.polarToCartesian(pp);
+		cartesian.setLocation(cartesian.getX()+centerX,cartesian.getY()+centerY);
+		return cartesian;
+	}
+	
+	private Point2D getMaxXY() {
+		double maxx = 0;
+		double maxy = 0;
+		for(Point2D p : locations.asMap().values()) {
+			maxx = Math.max(maxx, p.getX());
+			maxy = Math.max(maxy, p.getY());
+		}
+		return new Point2D.Double(maxx,maxy);
+	}
+	
+	private void setRadialLocations() {
+		Point2D max = getMaxXY();
+		double maxx = max.getX();
+		double maxy = max.getY();
+		maxx = Math.max(maxx, size.width);
+		double theta = 2*Math.PI/maxx;
+
+		double deltaRadius = size.width/2/maxy;
+		for(Map.Entry<V, Point2D> entry : locations.asMap().entrySet()) {
+			V v = entry.getKey();
+			Point2D p = entry.getValue();
+			PolarPoint polarPoint =
+					new PolarPoint(p.getX()*theta, (p.getY() - this.distY)*deltaRadius);
+			polarLocations.put(v, polarPoint);
+		}
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/RadiusGraphElementAccessor.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/RadiusGraphElementAccessor.java
new file mode 100644
index 0000000..4438a9b
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/RadiusGraphElementAccessor.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ *
+ * Created on Apr 12, 2005
+ */
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Shape;
+import java.awt.geom.Point2D;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import edu.uci.ics.jung.graph.Graph;
+
+
+/**
+ * Simple implementation of PickSupport that returns the vertex or edge
+ * that is closest to the specified location.  This implementation
+ * provides the same picking options that were available in
+ * previous versions of AbstractLayout.
+ * 
+ * <p>No element will be returned that is farther away than the specified 
+ * maximum distance.
+ * 
+ * @author Tom Nelson
+ * @author Joshua O'Madadhain
+ */
+public class RadiusGraphElementAccessor<V, E> implements GraphElementAccessor<V, E> {
+    
+    protected double maxDistance;
+    
+    /**
+     * Creates an instance with an effectively infinite default maximum distance.
+     */
+    public RadiusGraphElementAccessor() {
+        this(Math.sqrt(Double.MAX_VALUE - 1000));
+    }
+    
+    /**
+     * Creates an instance with the specified default maximum distance.
+     * @param maxDistance the maximum distance at which any element can be from a specified location
+     *     and still be returned
+     */
+    public RadiusGraphElementAccessor(double maxDistance) {
+        this.maxDistance = maxDistance;
+    }
+    
+	/**
+	 * Gets the vertex nearest to the location of the (x,y) location selected,
+	 * within a distance of <tt>maxDistance</tt>. Iterates through all
+	 * visible vertices and checks their distance from the click. Override this
+	 * method to provide a more efficient implementation.
+	 * 
+	 * @param layout the context in which the location is defined
+	 * @param x the x coordinate of the location
+	 * @param y the y coordinate of the location
+	 * @return a vertex which is associated with the location {@code (x,y)}
+	 *     as given by {@code layout}
+	 */
+	public V getVertex(Layout<V,E> layout, double x, double y) {
+	    return getVertex(layout, x, y, this.maxDistance);
+	}
+
+	/**
+	 * Gets the vertex nearest to the location of the (x,y) location selected,
+	 * within a distance of {@code maxDistance}. Iterates through all
+	 * visible vertices and checks their distance from the location. Override this
+	 * method to provide a more efficient implementation.
+	 * 
+	 * @param layout the context in which the location is defined
+	 * @param x the x coordinate of the location
+	 * @param y the y coordinate of the location
+	 * @param maxDistance the maximum distance at which any element can be from a specified location
+     *     and still be returned
+	 * @return a vertex which is associated with the location {@code (x,y)}
+	 *     as given by {@code layout}
+	 */
+	public V getVertex(Layout<V,E> layout, double x, double y, double maxDistance) {
+		double minDistance = maxDistance * maxDistance;
+        V closest = null;
+		while(true) {
+		    try {
+                for(V v : layout.getGraph().getVertices()) {
+
+		            Point2D p = layout.apply(v);
+		            double dx = p.getX() - x;
+		            double dy = p.getY() - y;
+		            double dist = dx * dx + dy * dy;
+		            if (dist < minDistance) {
+		                minDistance = dist;
+		                closest = v;
+		            }
+		        }
+		        break;
+		    } catch(ConcurrentModificationException cme) {}
+		}
+		return closest;
+	}
+	
+	public Collection<V> getVertices(Layout<V,E> layout, Shape rectangle) {
+		Set<V> pickedVertices = new HashSet<V>();
+		while(true) {
+		    try {
+                for(V v : layout.getGraph().getVertices()) {
+
+		            Point2D p = layout.apply(v);
+		            if(rectangle.contains(p)) {
+		            	pickedVertices.add(v);
+		            }
+		        }
+		        break;
+		    } catch(ConcurrentModificationException cme) {}
+		}
+		return pickedVertices;
+	}
+	
+	public E getEdge(Layout<V,E> layout, double x, double y) {
+	    return getEdge(layout, x, y, this.maxDistance);
+	}
+
+	/**
+	 * Gets the vertex nearest to the location of the (x,y) location selected,
+	 * whose endpoints are < {@code maxDistance}. Iterates through all
+	 * visible vertices and checks their distance from the location. Override this
+	 * method to provide a more efficient implementation.
+	 * 
+	 * @param layout the context in which the location is defined
+	 * @param x the x coordinate of the location
+	 * @param y the y coordinate of the location
+	 * @param maxDistance the maximum distance at which any element can be from a specified location
+     *     and still be returned
+	 * @return an edge which is associated with the location {@code (x,y)}
+	 *     as given by {@code layout}
+	 */
+	public E getEdge(Layout<V,E> layout, double x, double y, double maxDistance) {
+		double minDistance = maxDistance * maxDistance;
+		E closest = null;
+		while(true) {
+		    try {
+                for(E e : layout.getGraph().getEdges()) {
+
+		            // Could replace all this set stuff with getFrom_internal() etc.
+                    Graph<V, E> graph = layout.getGraph();
+		            Collection<V> vertices = graph.getIncidentVertices(e);
+		            Iterator<V> vertexIterator = vertices.iterator();
+		            V v1 = vertexIterator.next();
+		            V v2 = vertexIterator.next();
+		            // Get coords
+		            Point2D p1 = layout.apply(v1);
+		            Point2D p2 = layout.apply(v2);
+		            double x1 = p1.getX();
+		            double y1 = p1.getY();
+		            double x2 = p2.getX();
+		            double y2 = p2.getY();
+		            // Calculate location on line closest to (x,y)
+		            // First, check that v1 and v2 are not coincident.
+		            if (x1 == x2 && y1 == y2)
+		                continue;
+		            double b =
+		                ((y - y1) * (y2 - y1) + (x - x1) * (x2 - x1))
+		                / ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
+		            //
+		            double distance2; // square of the distance
+		            if (b <= 0)
+		                distance2 = (x - x1) * (x - x1) + (y - y1) * (y - y1);
+		            else if (b >= 1)
+		                distance2 = (x - x2) * (x - x2) + (y - y2) * (y - y2);
+		            else {
+		                double x3 = x1 + b * (x2 - x1);
+		                double y3 = y1 + b * (y2 - y1);
+		                distance2 = (x - x3) * (x - x3) + (y - y3) * (y - y3);
+		            }
+		            
+		            if (distance2 < minDistance) {
+		                minDistance = distance2;
+		                closest = e;
+		            }
+		        }
+		        break;
+		    } catch(ConcurrentModificationException cme) {}
+		}
+		return closest;
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/SpringLayout.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/SpringLayout.java
new file mode 100644
index 0000000..865bc57
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/SpringLayout.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Dimension;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.geom.Point2D;
+import java.util.ConcurrentModificationException;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer;
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * The SpringLayout package represents a visualization of a set of nodes. The
+ * SpringLayout, which is initialized with a Graph, assigns X/Y locations to
+ * each node. When called <code>relax()</code>, the SpringLayout moves the
+ * visualization forward one step.
+ *
+ * @author Danyel Fisher
+ * @author Joshua O'Madadhain
+ */
+public class SpringLayout<V, E> extends AbstractLayout<V,E> implements IterativeContext {
+
+    protected double stretch = 0.70;
+    protected Function<? super E, Integer> lengthFunction;
+    protected int repulsion_range_sq = 100 * 100;
+    protected double force_multiplier = 1.0 / 3.0;
+
+    protected LoadingCache<V, SpringVertexData> springVertexData =
+    	CacheBuilder.newBuilder().build(new CacheLoader<V, SpringVertexData>() {
+	    	public SpringVertexData load(V vertex) {
+	    		return new SpringVertexData();
+	    	}
+    });
+//    protected Map<V, SpringVertexData> springVertexData =
+//    	new MapMaker().makeComputingMap(new Function<V,SpringVertexData>(){
+//			public SpringVertexData apply(V arg0) {
+//				return new SpringVertexData();
+//			}});
+
+    /**
+     * Constructor for a SpringLayout for a raw graph with associated
+     * dimension--the input knows how big the graph is. Defaults to the unit
+     * length function.
+ 	 * @param g the graph on which the layout algorithm is to operate
+    */
+    @SuppressWarnings("unchecked")
+    public SpringLayout(Graph<V,E> g) {
+        this(g, (Function<E,Integer>)Functions.<Integer>constant(30));
+    }
+
+    /**
+     * Constructor for a SpringLayout for a raw graph with associated component.
+     *
+	 * @param g the graph on which the layout algorithm is to operate
+     * @param length_function provides a length for each edge
+     */
+    public SpringLayout(Graph<V,E> g, Function<? super E, Integer> length_function)
+    {
+        super(g);
+        this.lengthFunction = length_function;
+    }
+
+    /**
+     * @return the current value for the stretch parameter
+     */
+    public double getStretch() {
+        return stretch;
+    }
+
+	@Override
+	public void setSize(Dimension size) {
+		if(initialized == false)
+			setInitializer(new RandomLocationTransformer<V>(size));
+		super.setSize(size);
+	}
+
+    /**
+     * <p>Sets the stretch parameter for this instance.  This value
+     * specifies how much the degrees of an edge's incident vertices
+     * should influence how easily the endpoints of that edge
+     * can move (that is, that edge's tendency to change its length).
+     *
+     * <p>The default value is 0.70.  Positive values less than 1 cause
+     * high-degree vertices to move less than low-degree vertices, and
+     * values > 1 cause high-degree vertices to move more than
+     * low-degree vertices.  Negative values will have unpredictable
+     * and inconsistent results.
+     * @param stretch the stretch parameter
+     */
+    public void setStretch(double stretch) {
+        this.stretch = stretch;
+    }
+
+    public int getRepulsionRange() {
+        return (int)(Math.sqrt(repulsion_range_sq));
+    }
+
+    /**
+     * Sets the node repulsion range (in drawing area units) for this instance.
+     * Outside this range, nodes do not repel each other.  The default value
+     * is 100.  Negative values are treated as their positive equivalents.
+     * @param range the maximum repulsion range
+     */
+    public void setRepulsionRange(int range) {
+        this.repulsion_range_sq = range * range;
+    }
+
+    public double getForceMultiplier() {
+        return force_multiplier;
+    }
+
+    /**
+     * Sets the force multiplier for this instance.  This value is used to
+     * specify how strongly an edge "wants" to be its default length
+     * (higher values indicate a greater attraction for the default length),
+     * which affects how much its endpoints move at each timestep.
+     * The default value is 1/3.  A value of 0 turns off any attempt by the
+     * layout to cause edges to conform to the default length.  Negative
+     * values cause long edges to get longer and short edges to get shorter; use
+     * at your own risk.
+     * @param force an energy field created by all living things that binds the galaxy together
+     */
+    public void setForceMultiplier(double force) {
+        this.force_multiplier = force;
+    }
+
+    public void initialize() {
+    }
+
+    /**
+     * Relaxation step. Moves all nodes a smidge.
+     */
+    public void step() {
+    	try {
+    		for(V v : getGraph().getVertices()) {
+    			SpringVertexData svd = springVertexData.getUnchecked(v);
+    			if (svd == null) {
+    				continue;
+    			}
+    			svd.dx /= 4;
+    			svd.dy /= 4;
+    			svd.edgedx = svd.edgedy = 0;
+    			svd.repulsiondx = svd.repulsiondy = 0;
+    		}
+    	} catch(ConcurrentModificationException cme) {
+    		step();
+    	}
+
+    	relaxEdges();
+    	calculateRepulsion();
+    	moveNodes();
+    }
+
+    protected void relaxEdges() {
+    	try {
+    		for(E e : getGraph().getEdges()) {
+    		    Pair<V> endpoints = getGraph().getEndpoints(e);
+    			V v1 = endpoints.getFirst();
+    			V v2 = endpoints.getSecond();
+
+    			Point2D p1 = apply(v1);
+    			Point2D p2 = apply(v2);
+    			if(p1 == null || p2 == null) continue;
+    			double vx = p1.getX() - p2.getX();
+    			double vy = p1.getY() - p2.getY();
+    			double len = Math.sqrt(vx * vx + vy * vy);
+
+    			double desiredLen = lengthFunction.apply(e);
+
+    			// round from zero, if needed [zero would be Bad.].
+    			len = (len == 0) ? .0001 : len;
+
+    			double f = force_multiplier * (desiredLen - len) / len;
+
+    			f = f * Math.pow(stretch, (getGraph().degree(v1) + getGraph().degree(v2) - 2));
+
+    			// the actual movement distance 'dx' is the force multiplied by the
+    			// distance to go.
+    			double dx = f * vx;
+    			double dy = f * vy;
+    			SpringVertexData v1D, v2D;
+    			v1D = springVertexData.getUnchecked(v1);
+    			v2D = springVertexData.getUnchecked(v2);
+
+    			v1D.edgedx += dx;
+    			v1D.edgedy += dy;
+    			v2D.edgedx += -dx;
+    			v2D.edgedy += -dy;
+    		}
+    	} catch(ConcurrentModificationException cme) {
+    		relaxEdges();
+    	}
+    }
+
+    protected void calculateRepulsion() {
+        try {
+        for (V v : getGraph().getVertices()) {
+            if (isLocked(v)) continue;
+
+            SpringVertexData svd = springVertexData.getUnchecked(v);
+            if(svd == null) continue;
+            double dx = 0, dy = 0;
+
+            for (V v2 : getGraph().getVertices()) {
+                if (v == v2) continue;
+                Point2D p = apply(v);
+                Point2D p2 = apply(v2);
+                if(p == null || p2 == null) continue;
+                double vx = p.getX() - p2.getX();
+                double vy = p.getY() - p2.getY();
+                double distanceSq = p.distanceSq(p2);
+                if (distanceSq == 0) {
+                    dx += Math.random();
+                    dy += Math.random();
+                } else if (distanceSq < repulsion_range_sq) {
+                    double factor = 1;
+                    dx += factor * vx / distanceSq;
+                    dy += factor * vy / distanceSq;
+                }
+            }
+            double dlen = dx * dx + dy * dy;
+            if (dlen > 0) {
+                dlen = Math.sqrt(dlen) / 2;
+                svd.repulsiondx += dx / dlen;
+                svd.repulsiondy += dy / dlen;
+            }
+        }
+        } catch(ConcurrentModificationException cme) {
+            calculateRepulsion();
+        }
+    }
+
+    protected void moveNodes()
+    {
+        synchronized (getSize()) {
+            try {
+                for (V v : getGraph().getVertices()) {
+                    if (isLocked(v)) continue;
+                    SpringVertexData vd = springVertexData.getUnchecked(v);
+                    if(vd == null) continue;
+                    Point2D xyd = apply(v);
+
+                    vd.dx += vd.repulsiondx + vd.edgedx;
+                    vd.dy += vd.repulsiondy + vd.edgedy;
+
+                    // keeps nodes from moving any faster than 5 per time unit
+                    xyd.setLocation(xyd.getX()+Math.max(-5, Math.min(5, vd.dx)),
+                    		xyd.getY()+Math.max(-5, Math.min(5, vd.dy)));
+
+                    Dimension d = getSize();
+                    int width = d.width;
+                    int height = d.height;
+
+                    if (xyd.getX() < 0) {
+                        xyd.setLocation(0, xyd.getY());
+                    } else if (xyd.getX() > width) {
+                        xyd.setLocation(width, xyd.getY());
+                    }
+                    if (xyd.getY() < 0) {
+                        xyd.setLocation(xyd.getX(), 0);
+                    } else if (xyd.getY() > height) {
+                        xyd.setLocation(xyd.getX(), height);
+                    }
+
+                }
+            } catch(ConcurrentModificationException cme) {
+                moveNodes();
+            }
+        }
+    }
+
+    protected static class SpringVertexData {
+        protected double edgedx;
+        protected double edgedy;
+        protected double repulsiondx;
+        protected double repulsiondy;
+
+        /** movement speed, x */
+        protected double dx;
+
+        /** movement speed, y */
+        protected double dy;
+    }
+
+
+    /**
+     * Used for changing the size of the layout in response to a component's size.
+     */
+    public class SpringDimensionChecker extends ComponentAdapter {
+        @Override
+        public void componentResized(ComponentEvent e) {
+            setSize(e.getComponent().getSize());
+        }
+    }
+
+    /**
+     * @return true
+     */
+    public boolean isIncremental() {
+        return true;
+    }
+
+    /**
+     * @return false
+     */
+    public boolean done() {
+        return false;
+    }
+
+    /**
+     * No effect.
+     */
+	public void reset() {
+	}
+}
\ No newline at end of file
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/SpringLayout2.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/SpringLayout2.java
new file mode 100644
index 0000000..be7dbcd
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/SpringLayout2.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+import java.util.ConcurrentModificationException;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * The SpringLayout package represents a visualization of a set of nodes. The
+ * SpringLayout, which is initialized with a Graph, assigns X/Y locations to
+ * each node. When called <code>relax()</code>, the SpringLayout moves the
+ * visualization forward one step.
+ * 
+ * 
+ * 
+ * @author Danyel Fisher
+ * @author Joshua O'Madadhain
+ */
+public class SpringLayout2<V, E> extends SpringLayout<V,E> 
+{
+    protected int currentIteration;
+    protected int averageCounter;
+    protected int loopCountMax = 4;
+    protected boolean done;
+    
+    protected Point2D averageDelta = new Point2D.Double();
+    
+    /**
+     * Constructor for a SpringLayout for a raw graph with associated
+     * dimension--the input knows how big the graph is. Defaults to the unit
+     * length function.
+	 * @param g the graph on which the layout algorithm is to operate
+     */
+    public SpringLayout2(Graph<V,E> g) {
+        super(g);
+    }
+
+    /**
+     * Constructor for a SpringLayout for a raw graph with associated component.
+     *
+     * @param g the {@code Graph} to lay out
+     * @param length_function provides a length for each edge
+     */
+    public SpringLayout2(Graph<V,E> g, Function<E, Integer> length_function)
+    {
+        super(g, length_function);
+    }
+
+    /**
+     * Relaxation step. Moves all nodes a smidge.
+     */
+    @Override
+    public void step() {
+        super.step();
+    	currentIteration++;
+    	testAverageDeltas();
+    }
+    
+    private void testAverageDeltas() {
+    	double dx = this.averageDelta.getX();
+    	double dy = this.averageDelta.getY();
+    	if(Math.abs(dx) < .001 && Math.abs(dy) < .001) {
+    		done = true;
+    		System.err.println("done, dx="+dx+", dy="+dy);
+    	}
+        if(currentIteration > loopCountMax) {
+        	this.averageDelta.setLocation(0,0);
+        	averageCounter = 0;
+        	currentIteration = 0;
+        }
+    }
+
+    @Override
+    protected void moveNodes() {
+        synchronized (getSize()) {
+            try {
+                for (V v : getGraph().getVertices()) {
+                    if (isLocked(v)) continue;
+                    SpringVertexData vd = springVertexData.getUnchecked(v);
+                    if(vd == null) continue;
+                    Point2D xyd = apply(v);
+                    
+                    vd.dx += vd.repulsiondx + vd.edgedx;
+                    vd.dy += vd.repulsiondy + vd.edgedy;
+                    
+//                    int currentCount = currentIteration % this.loopCountMax;
+//                    System.err.println(averageCounter+" --- vd.dx="+vd.dx+", vd.dy="+vd.dy);
+//                    System.err.println("averageDelta was "+averageDelta);
+
+                    averageDelta.setLocation(
+                    		((averageDelta.getX() * averageCounter) + vd.dx) / (averageCounter+1),
+                    		((averageDelta.getY() * averageCounter) + vd.dy) / (averageCounter+1)
+                    		);
+//                    System.err.println("averageDelta now "+averageDelta);
+//                    System.err.println();
+                    averageCounter++;
+                    
+                    // keeps nodes from moving any faster than 5 per time unit
+                    xyd.setLocation(xyd.getX()+Math.max(-5, Math.min(5, vd.dx)),
+                    		xyd.getY()+Math.max(-5, Math.min(5, vd.dy)));
+                    
+                    Dimension d = getSize();
+                    int width = d.width;
+                    int height = d.height;
+                    
+                    if (xyd.getX() < 0) {
+                        xyd.setLocation(0, xyd.getY());//                     setX(0);
+                    } else if (xyd.getX() > width) {
+                        xyd.setLocation(width, xyd.getY());             //setX(width);
+                    }
+                    if (xyd.getY() < 0) {
+                        xyd.setLocation(xyd.getX(),0);//setY(0);
+                    } else if (xyd.getY() > height) {
+                        xyd.setLocation(xyd.getX(), height);      //setY(height);
+                    }
+                    
+                }
+            } catch(ConcurrentModificationException cme) {
+                moveNodes();
+            }
+        }
+    }
+
+    @Override
+    public boolean done() {
+        return done;
+    }
+
+}
\ No newline at end of file
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/StaticLayout.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/StaticLayout.java
new file mode 100644
index 0000000..a995833
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/StaticLayout.java
@@ -0,0 +1,49 @@
+/*
+ * Created on Jul 21, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * StaticLayout places the vertices in the locations specified by its initializer,
+ * and has no other behavior.
+ * Vertex locations can be placed in a {@code Map<V,Point2D>} and then supplied to
+ * this layout as follows: {@code Function<V,Point2D> vertexLocations = Functions.forMap(map);}
+ * @author Tom Nelson - tomnelson at dev.java.net
+ */
+public class StaticLayout<V, E> extends AbstractLayout<V,E> {
+	
+    public StaticLayout(Graph<V,E> graph, Function<V,Point2D> initializer, Dimension size) {
+        super(graph, initializer, size);
+    }
+    
+    public StaticLayout(Graph<V,E> graph, Function<V,Point2D> initializer) {
+        super(graph, initializer);
+    }
+    
+    public StaticLayout(Graph<V,E> graph) {
+    	super(graph);
+    }
+    
+    public StaticLayout(Graph<V,E> graph, Dimension size) {
+    	super(graph, size);
+    }
+    
+    public void initialize() {}
+
+	public void reset() {}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/TreeLayout.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/TreeLayout.java
new file mode 100644
index 0000000..62f3d3c
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/TreeLayout.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 9, 2005
+ */
+
+package edu.uci.ics.jung.algorithms.layout;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.geom.Point2D;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Function;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import edu.uci.ics.jung.graph.Forest;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.TreeUtils;
+
+/**
+ * @author Karlheinz Toni
+ * @author Tom Nelson - converted to jung2
+ */
+public class TreeLayout<V,E> implements Layout<V,E> {
+
+	protected Dimension size = new Dimension(600,600);
+	protected Forest<V,E> graph;
+	protected Map<V,Integer> basePositions = new HashMap<V,Integer>();
+
+    protected LoadingCache<V, Point2D> locations =
+    	CacheBuilder.newBuilder().build(new CacheLoader<V, Point2D>() {
+	    	public Point2D load(V vertex) {
+	    		return new Point2D.Double();
+	    	}
+    });
+    
+    protected transient Set<V> alreadyDone = new HashSet<V>();
+
+    /**
+     * The default horizontal vertex spacing.  Initialized to 50.
+     */
+    public static int DEFAULT_DISTX = 50;
+    
+    /**
+     * The default vertical vertex spacing.  Initialized to 50.
+     */
+    public static int DEFAULT_DISTY = 50;
+    
+    /**
+     * The horizontal vertex spacing.  Defaults to {@code DEFAULT_XDIST}.
+     */
+    protected int distX = 50;
+    
+    /**
+     * The vertical vertex spacing.  Defaults to {@code DEFAULT_YDIST}.
+     */
+    protected int distY = 50;
+    
+    protected transient Point m_currentPoint = new Point();
+
+    /**
+     * Creates an instance for the specified graph with default X and Y distances.
+	 * @param g the graph on which the layout algorithm is to operate
+     */
+    public TreeLayout(Forest<V,E> g) {
+    	this(g, DEFAULT_DISTX, DEFAULT_DISTY);
+    }
+
+    /**
+     * Creates an instance for the specified graph and X distance with
+     * default Y distance.
+	 * @param g the graph on which the layout algorithm is to operate
+	 * @param distx the horizontal spacing between adjacent siblings
+     */
+    public TreeLayout(Forest<V,E> g, int distx) {
+        this(g, distx, DEFAULT_DISTY);
+    }
+
+    /**
+     * Creates an instance for the specified graph, X distance, and Y distance.
+	 * @param g the graph on which the layout algorithm is to operate
+	 * @param distx the horizontal spacing between adjacent siblings
+	 * @param disty the vertical spacing between adjacent siblings
+     */
+    public TreeLayout(Forest<V,E> g, int distx, int disty) {
+        if (g == null)
+            throw new IllegalArgumentException("Graph must be non-null");
+        if (distx < 1 || disty < 1)
+            throw new IllegalArgumentException("X and Y distances must each be positive");
+    	this.graph = g;
+        this.distX = distx;
+        this.distY = disty;
+        buildTree();
+    }
+    
+	protected void buildTree() {
+        this.m_currentPoint = new Point(0, 20);
+        Collection<V> roots = TreeUtils.getRoots(graph);
+        if (roots.size() > 0 && graph != null) {
+       		calculateDimensionX(roots);
+       		for(V v : roots) {
+        		calculateDimensionX(v);
+        		m_currentPoint.x += this.basePositions.get(v)/2 + this.distX;
+        		buildTree(v, this.m_currentPoint.x);
+        	}
+        }
+    }
+
+    protected void buildTree(V v, int x) {
+
+        if (alreadyDone.add(v)) {
+            //go one level further down
+            this.m_currentPoint.y += this.distY;
+            this.m_currentPoint.x = x;
+
+            this.setCurrentPositionFor(v);
+
+            int sizeXofCurrent = basePositions.get(v);
+
+            int lastX = x - sizeXofCurrent / 2;
+
+            int sizeXofChild;
+            int startXofChild;
+
+            for (V element : graph.getSuccessors(v)) {
+                sizeXofChild = this.basePositions.get(element);
+                startXofChild = lastX + sizeXofChild / 2;
+                buildTree(element, startXofChild);
+                lastX = lastX + sizeXofChild + distX;
+            }
+            this.m_currentPoint.y -= this.distY;
+        }
+    }
+    
+    private int calculateDimensionX(V v) {
+
+        int size = 0;
+        int childrenNum = graph.getSuccessors(v).size();
+
+        if (childrenNum != 0) {
+            for (V element : graph.getSuccessors(v)) {
+                size += calculateDimensionX(element) + distX;
+            }
+        }
+        size = Math.max(0, size - distX);
+        basePositions.put(v, size);
+
+        return size;
+    }
+
+    private int calculateDimensionX(Collection<V> roots) {
+
+    	int size = 0;
+    	for(V v : roots) {
+    		int childrenNum = graph.getSuccessors(v).size();
+
+    		if (childrenNum != 0) {
+    			for (V element : graph.getSuccessors(v)) {
+    				size += calculateDimensionX(element) + distX;
+    			}
+    		}
+    		size = Math.max(0, size - distX);
+    		basePositions.put(v, size);
+    	}
+
+    	return size;
+    }
+    
+    /**
+     * This method is not supported by this class.  The size of the layout
+     * is determined by the topology of the tree, and by the horizontal 
+     * and vertical spacing (optionally set by the constructor).
+     */
+    public void setSize(Dimension size) {
+        throw new UnsupportedOperationException("Size of TreeLayout is set" +
+                " by vertex spacing in constructor");
+    }
+
+    protected void setCurrentPositionFor(V vertex) {
+    	int x = m_currentPoint.x;
+    	int y = m_currentPoint.y;
+    	if(x < 0) size.width -= x;
+    	
+    	if(x > size.width-distX) 
+    		size.width = x + distX;
+    	
+    	if(y < 0) size.height -= y;
+    	if(y > size.height-distY) 
+    		size.height = y + distY;
+    	locations.getUnchecked(vertex).setLocation(m_currentPoint);
+
+    }
+
+	public Graph<V,E> getGraph() {
+		return graph;
+	}
+
+	public Dimension getSize() {
+		return size;
+	}
+
+	public void initialize() {
+
+	}
+
+	public boolean isLocked(V v) {
+		return false;
+	}
+
+	public void lock(V v, boolean state) {
+	}
+
+	public void reset() {
+	}
+
+	public void setGraph(Graph<V,E> graph) {
+		if(graph instanceof Forest) {
+			this.graph = (Forest<V,E>)graph;
+			buildTree();
+		} else {
+			throw new IllegalArgumentException("graph must be a Forest");
+		}
+	}
+
+	public void setInitializer(Function<V, Point2D> initializer) {
+	}
+	
+    /**
+     * @return the center of this layout's area.
+     */
+	public Point2D getCenter() {
+		return new Point2D.Double(size.getWidth()/2,size.getHeight()/2);
+	}
+
+	public void setLocation(V v, Point2D location) {
+		locations.getUnchecked(v).setLocation(location);
+	}
+	
+	public Point2D apply(V v) {
+		return locations.getUnchecked(v);
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/package.html
new file mode 100644
index 0000000..12f31f5
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/package.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Algorithms for assigning 2D coordinates (typically used for graph visualizations) 
+to vertices.
+Current layout algorithms include:
+<ul>
+<li><code>Layout, AbstractLayout</code>: interface and abstract class defining the Layout contract and handling
+some common implementation details
+<li><code>AggregateLayout</code>: allows multiple layouts to be combined and manipulated as one layout
+<li><code>BalloonLayout</code>: places vertices on nested circles (trees/forests only)
+<li><code>CircleLayout</code>: places vertices on a circle
+<li><code>DAGLayout</code>: places vertices in a hierarchy (directed acyclic graphs only)
+<li><code>FRLayout</code>: Fruchterman-Reingold algorithm (force-directed)
+<li><code>ISOMLayout</code>: self-organizing map layout
+<li><code>KKLayout</code>: Kamada-Kawai algorithm (tries to maintain specified distances)
+<li><code>RadialTreeLayout</code>: places vertices on concentric circles (trees only)
+<li><code>SpringLayout</code>: simple force-directed layout
+<li><code>StaticLayout</code>: places vertices at user-specified locations
+<li><code>TreeLayout</code>: simple tree/forest layout
+</ul>
+
+Rendering and other aspects of visualization are handled in the <code>visualization</code> package.
+
+</body>
+</html>
+
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/RandomLocationTransformer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/RandomLocationTransformer.java
new file mode 100644
index 0000000..310bc6d
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/RandomLocationTransformer.java
@@ -0,0 +1,62 @@
+/*
+ * Created on Jul 19, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.layout.util;
+
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+import java.util.Date;
+import java.util.Random;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.layout.StaticLayout;
+
+/**
+ * Provides a random vertex location within the bounds of the Dimension property.
+ * This provides a random location for unmapped vertices
+ * the first time they are accessed.
+ * 
+ * <p><b>Note</b>: the generated values are not cached, so apply() will generate a new random
+ * location for the passed vertex every time it is called.  If you want a consistent value,
+ * wrap this layout's generated values in a {@link StaticLayout} instance.
+ * 
+ * @author Tom Nelson
+ *
+ * @param <V> the vertex type
+ */
+public class RandomLocationTransformer<V> implements Function<V,Point2D> {
+	Dimension d;
+	Random random;
+    
+    /**
+     * Creates an instance with the specified size which uses the current time 
+     * as the random seed.
+     * @param d the size of the layout area
+     */
+    public RandomLocationTransformer(Dimension d) {
+    	this(d, new Date().getTime());
+    }
+    
+    /**
+     * Creates an instance with the specified dimension and random seed.
+     * @param d the size of the layout area
+     * @param seed the seed for the internal random number generator
+     */
+    public RandomLocationTransformer(final Dimension d, long seed) {
+    	this.d = d;
+    	this.random = new Random(seed);
+    }
+    
+    public Point2D apply(V v) {
+        return new Point2D.Double(random.nextDouble() * d.width, random.nextDouble() * d.height);
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/Relaxer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/Relaxer.java
new file mode 100644
index 0000000..79dba9f
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/Relaxer.java
@@ -0,0 +1,43 @@
+package edu.uci.ics.jung.algorithms.layout.util;
+
+/**
+ * Interface for operating the relax iterations on a layout.
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ */
+public interface Relaxer {
+	
+	/**
+	 * Execute a loop of steps in a new Thread,
+	 * firing an event after each step.
+	 */
+	void relax();
+	
+	/**
+	 * Execute a loop of steps in the calling
+	 * thread, firing no events.
+	 */
+	void prerelax();
+	
+	/**
+	 * Make the relaxer thread wait.
+	 */
+	void pause();
+	
+	/**
+	 * Make the relaxer thread resume.
+	 *
+	 */
+	void resume();
+	
+	/**
+	 * Set flags to stop the relaxer thread.
+	 */
+	void stop();
+
+	/**
+	 * @param i the sleep time between iterations, in milliseconds
+	 */
+	void setSleepTime(long i);
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/VisRunner.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/VisRunner.java
new file mode 100644
index 0000000..4fbb905
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/VisRunner.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * 
+ */
+package edu.uci.ics.jung.algorithms.layout.util;
+
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+
+/**
+ * 
+ * Implementation of a relaxer thread for layouts.
+ * Extracted from the {@code VisualizationModel} in previous
+ * versions of JUNG.
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ */
+public class VisRunner implements Relaxer, Runnable {
+	
+	protected boolean running;
+	protected IterativeContext process;
+	protected boolean stop;
+	protected boolean manualSuspend;
+	protected Thread thread;
+	
+	/**
+	 * how long the relaxer thread pauses between iteration loops.
+	 */
+	protected long sleepTime = 100L;
+
+	
+	/**
+	 * Creates an instance for the specified process.
+	 * @param process the process (generally a layout) for which this instance is created
+	 */
+	public VisRunner(IterativeContext process) {
+		this.process = process;
+	}
+
+	/**
+	 * @return the relaxerThreadSleepTime
+	 */
+	public long getSleepTime() {
+		return sleepTime;
+	}
+
+	/**
+	 * @param sleepTime the sleep time to set for this thread
+	 */
+	public void setSleepTime(long sleepTime) {
+		this.sleepTime = sleepTime;
+	}
+	
+	public void prerelax() {
+		manualSuspend = true;
+		long timeNow = System.currentTimeMillis();
+		while (System.currentTimeMillis() - timeNow < 500 && !process.done()) {
+			process.step();
+		}
+		manualSuspend = false;
+	}
+
+	public void pause() {
+		manualSuspend = true;
+	}
+
+	public void relax() {
+		// in case its running
+		stop();
+		stop = false;
+		thread = new Thread(this);
+		thread.setPriority(Thread.MIN_PRIORITY);
+		thread.start();
+	}
+	
+	/**
+	 * Used for synchronization.
+	 */
+	public Object pauseObject = new String("PAUSE OBJECT");
+
+	public void resume() {
+		manualSuspend = false;
+		if(running == false) {
+			prerelax();
+			relax();
+		} else {
+			synchronized(pauseObject) {
+				pauseObject.notifyAll();
+			}
+		}
+	}
+
+	public synchronized void stop() {
+		if(thread != null) {
+			manualSuspend = false;
+			stop = true;
+			// interrupt the relaxer, in case it is paused or sleeping
+			// this should ensure that visRunnerIsRunning gets set to false
+			try { thread.interrupt(); }
+			catch(Exception ex) {
+				// the applet security manager may have prevented this.
+				// just sleep for a second to let the thread stop on its own
+				try { Thread.sleep(1000); }
+				catch(InterruptedException ie) {} // ignore
+			}
+			synchronized (pauseObject) {
+				pauseObject.notifyAll();
+			}
+		}
+	}
+
+	public void run() {
+	    running = true;
+	    try {
+	        while (!process.done() && !stop) {
+	            synchronized (pauseObject) {
+	                while (manualSuspend && !stop) {
+	                    try {
+	                        pauseObject.wait();
+	                    } catch (InterruptedException e) {
+	                    	// ignore
+	                    }
+	                }
+	            }
+	            process.step();
+	            
+	            if (stop)
+	                return;
+	            
+	            try {
+	                Thread.sleep(sleepTime);
+	            } catch (InterruptedException ie) {
+	            	// ignore
+	            }
+	        }
+
+	    } finally {
+	        running = false;
+	    }
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/package.html
new file mode 100644
index 0000000..7638300
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/layout/util/package.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Utility classes for updating layout positions.
+
+</body>
+</html>
+
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/Metrics.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/Metrics.java
new file mode 100644
index 0000000..ab11118
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/Metrics.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Jun 7, 2008
+ * 
+ */
+package edu.uci.ics.jung.algorithms.metrics;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * A class consisting of static methods for calculating graph metrics.
+ */
+public class Metrics 
+{
+    /**
+     * Returns a <code>Map</code> of vertices to their clustering coefficients.
+     * The clustering coefficient cc(v) of a vertex v is defined as follows:
+     * <ul>
+     * <li><code>degree(v) == {0,1}</code>: 0
+     * <li><code>degree(v) == n, n >= 2</code>: given S, the set of neighbors
+     * of <code>v</code>: cc(v) = (the sum over all w in S of the number of 
+     * other elements of w that are neighbors of w) / ((|S| * (|S| - 1) / 2).
+     * Less formally, the fraction of <code>v</code>'s neighbors that are also
+     * neighbors of each other. 
+     * </ul>
+     * <p><b>Note</b>: This algorithm treats its argument as an undirected graph;
+     * edge direction is ignored. 
+     * @param graph the graph whose clustering coefficients are to be calculated
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @return the clustering coefficient for each vertex
+     * @see "The structure and function of complex networks, M.E.J. Newman, aps.arxiv.org/abs/cond-mat/0303516"
+     */
+    public static <V,E> Map<V, Double> clusteringCoefficients(Graph<V,E> graph)
+    {
+        Map<V,Double> coefficients = new HashMap<V,Double>();
+        
+        for (V v : graph.getVertices())
+        {
+            int n = graph.getNeighborCount(v);
+            if (n < 2)
+                coefficients.put(v, new Double(0));
+            else
+            {
+                // how many of v's neighbors are connected to each other?
+                ArrayList<V> neighbors = new ArrayList<V>(graph.getNeighbors(v));
+                double edge_count = 0;
+                for (int i = 0; i < n; i++)
+                {
+                    V w = neighbors.get(i);
+                    for (int j = i+1; j < n; j++ )
+                    {
+                        V x = neighbors.get(j);
+                        edge_count += graph.isNeighbor(w, x) ? 1 : 0;
+                    }
+                }
+                double possible_edges = (n * (n - 1))/2.0;
+                coefficients.put(v, new Double(edge_count / possible_edges));
+            }
+        }
+        
+        return coefficients;
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/StructuralHoles.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/StructuralHoles.java
new file mode 100644
index 0000000..bc4dd7c
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/StructuralHoles.java
@@ -0,0 +1,343 @@
+/*
+ * Created on Sep 19, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.metrics;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * Calculates some of the measures from Burt's text "Structural Holes: 
+ * The Social Structure of Competition".
+ * 
+ * <p><b>Notes</b>: 
+ * <ul>
+ * <li>Each of these measures assumes that each edge has an associated 
+ * non-null weight whose value is accessed through the specified 
+ * <code>Transformer</code> instance.
+ * <li>Nonexistent edges are treated as edges with weight 0 for purposes 
+ * of edge weight calculations.
+ * </ul>
+ *  
+ * <p>Based on code donated by Jasper Voskuilen and 
+ * Diederik van Liere of the Department of Information and Decision Sciences
+ * at Erasmus University.
+ * 
+ * @author Joshua O'Madadhain
+ * @author Jasper Voskuilen
+ * @see "Ronald Burt, Structural Holes: The Social Structure of Competition"
+ * @author Tom Nelson - converted to jung2
+ */
+public class StructuralHoles<V,E> {
+	
+    protected Function<E, ? extends Number> edge_weight;
+    protected Graph<V,E> g;
+    
+    /**
+     * @param graph the graph for which the metrics are to be calculated
+     * @param nev the edge weights
+     */
+    public StructuralHoles(Graph<V,E> graph, Function<E, ? extends Number> nev) 
+    {
+        this.g = graph;
+        this.edge_weight = nev;
+    }
+
+    /**
+     * Burt's measure of the effective size of a vertex's network.  Essentially, the
+     * number of neighbors minus the average degree of those in <code>v</code>'s neighbor set,
+     * not counting ties to <code>v</code>.  Formally: 
+     * <pre>
+     * effectiveSize(v) = v.degree() - (sum_{u in N(v)} sum_{w in N(u), w !=u,v} p(v,w)*m(u,w))
+     * </pre>
+     * where 
+     * <ul>
+     * <li><code>N(a) = a.getNeighbors()</code>
+     * <li><code>p(v,w) =</code> normalized mutual edge weight of v and w
+     * <li><code>m(u,w)</code> = maximum-scaled mutual edge weight of u and w
+     * </ul>
+     * @param v the vertex whose properties are being measured
+     * @return the effective size of the vertex's network
+     * 
+     * @see #normalizedMutualEdgeWeight(Object, Object)
+     * @see #maxScaledMutualEdgeWeight(Object, Object) 
+     */
+    public double effectiveSize(V v)
+    {
+        double result = g.degree(v);
+        for(V u : g.getNeighbors(v)) {
+
+            for(V w : g.getNeighbors(u)) {
+
+                if (w != v && w != u)
+                    result -= normalizedMutualEdgeWeight(v,w) * 
+                              maxScaledMutualEdgeWeight(u,w);
+            }
+        }
+        return result;
+    }
+    
+    /**
+     * Returns the effective size of <code>v</code> divided by the number of 
+     * alters in <code>v</code>'s network.  (In other words, 
+     * <code>effectiveSize(v) / v.degree()</code>.)
+     * If <code>v.degree() == 0</code>, returns 0.
+     * 
+     * @param v the vertex whose properties are being measured
+     * @return the effective size of the vertex divided by its degree
+     */
+    public double efficiency(V v) {
+        double degree = g.degree(v);
+        
+        if (degree == 0)
+            return 0;
+        else
+            return effectiveSize(v) / degree;
+    }
+
+    /**
+     * Burt's constraint measure (equation 2.4, page 55 of Burt, 1992). Essentially a
+     * measure of the extent to which <code>v</code> is invested in people who are invested in
+     * other of <code>v</code>'s alters (neighbors).  The "constraint" is characterized
+     * by a lack of primary holes around each neighbor.  Formally:
+     * <pre>
+     * constraint(v) = sum_{w in MP(v), w != v} localConstraint(v,w)
+     * </pre>
+     * where MP(v) is the subset of v's neighbors that are both predecessors and successors of v. 
+     * @see #localConstraint(Object, Object)
+     * 
+     * @param v the vertex whose properties are being measured
+     * @return the constraint of the vertex
+     */
+    public double constraint(V v) {
+        double result = 0;
+        for(V w : g.getSuccessors(v)) {
+
+            if (v != w && g.isPredecessor(v,w))
+            {
+                result += localConstraint(v, w);
+            }
+        }
+
+        return result;
+    }
+
+    
+    /**
+     * Calculates the hierarchy value for a given vertex.  Returns <code>NaN</code> when
+     * <code>v</code>'s degree is 0, and 1 when <code>v</code>'s degree is 1.
+     * Formally:
+     * <pre>
+     * hierarchy(v) = (sum_{v in N(v), w != v} s(v,w) * log(s(v,w))}) / (v.degree() * Math.log(v.degree()) 
+     * </pre>
+     * where
+     * <ul>
+     * <li><code>N(v) = v.getNeighbors()</code> 
+     * <li><code>s(v,w) = localConstraint(v,w) / (aggregateConstraint(v) / v.degree())</code>
+     * </ul>
+     * @see #localConstraint(Object, Object)
+     * @see #aggregateConstraint(Object)
+     * 
+     * @param v the vertex whose properties are being measured
+     * @return the hierarchy value for a given vertex
+     */
+    public double hierarchy(V v)
+    {
+        double v_degree = g.degree(v);
+        
+        if (v_degree == 0)
+            return Double.NaN;
+        if (v_degree == 1)
+            return 1;
+        
+        double v_constraint = aggregateConstraint(v);
+
+        double numerator = 0;
+        for (V w : g.getNeighbors(v)) {
+        
+            if (v != w)
+            {
+                double sl_constraint = localConstraint(v, w) / (v_constraint / v_degree);
+                numerator += sl_constraint * Math.log(sl_constraint);
+            }
+        }
+
+        return numerator / (v_degree * Math.log(v_degree));
+    }
+
+    /**
+     * Returns the local constraint on <code>v1</code> from a lack of primary holes 
+     * around its neighbor <code>v2</code>.
+     * Based on Burt's equation 2.4.  Formally:
+     * <pre>
+     * localConstraint(v1, v2) = ( p(v1,v2) + ( sum_{w in N(v)} p(v1,w) * p(w, v2) ) )^2
+     * </pre>
+     * where 
+     * <ul>
+     * <li><code>N(v) = v.getNeighbors()</code>
+     * <li><code>p(v,w) =</code> normalized mutual edge weight of v and w
+     * </ul>
+     * @param v1 the first vertex whose local constraint is desired
+     * @param v2 the second vertex whose local constraint is desired
+     * @return the local constraint on (v1, v2)
+     * @see #normalizedMutualEdgeWeight(Object, Object)
+     */
+    public double localConstraint(V v1, V v2) 
+    {
+        double nmew_vw = normalizedMutualEdgeWeight(v1, v2);
+        double inner_result = 0;
+        for (V w : g.getNeighbors(v1)) {
+
+            inner_result += normalizedMutualEdgeWeight(v1,w) * 
+                normalizedMutualEdgeWeight(w,v2);
+        }
+        return (nmew_vw + inner_result) * (nmew_vw + inner_result);
+    }
+    
+    /**
+     * The aggregate constraint on <code>v</code>.  Based on Burt's equation 2.7.  
+     * Formally:
+     * <pre>
+     * aggregateConstraint(v) = sum_{w in N(v)} localConstraint(v,w) * O(w)
+     * </pre>
+     * where
+     * <ul>
+     * <li><code>N(v) = v.getNeighbors()</code>
+     * <li><code>O(w) = organizationalMeasure(w)</code>
+     * </ul>
+     * 
+     * @param v the vertex whose properties are being measured
+     * @return the aggregate constraint on v
+     */
+    public double aggregateConstraint(V v)
+    {
+        double result = 0;
+        for (V w : g.getNeighbors(v)) {
+
+            result += localConstraint(v, w) * organizationalMeasure(g, w);
+        }
+        return result;
+    }
+    
+    /**
+     * A measure of the organization of individuals within the subgraph 
+     * centered on <code>v</code>.  Burt's text suggests that this is 
+     * in some sense a measure of how "replaceable" <code>v</code> is by 
+     * some other element of this subgraph.  Should be a number in the
+     * closed interval [0,1].
+     * 
+     * <p>This implementation returns 1.  Users may wish to override this
+     * method in order to define their own behavior.
+     * @param g the subgraph centered on v
+     * @param v the vertex whose properties are being measured
+     * @return 1.0 (in this implementation)
+     */
+    protected double organizationalMeasure(Graph<V,E> g, V v) {
+        return 1.0;
+    }
+    
+   
+    /**
+     * Returns the proportion of <code>v1</code>'s network time and energy invested
+     * in the relationship with <code>v2</code>.  Formally:
+     * <pre>
+     * normalizedMutualEdgeWeight(a,b) = mutual_weight(a,b) / (sum_c mutual_weight(a,c))
+     * </pre>
+     * Returns 0 if either numerator or denominator = 0, or if <code>v1 == v2</code>.
+     * @see #mutualWeight(Object, Object)
+     * @param v1 the first vertex of the pair whose property is being measured
+     * @param v2 the second vertex of the pair whose property is being measured
+     * @return the normalized mutual edge weight between v1 and v2
+     */
+    protected double normalizedMutualEdgeWeight(V v1, V v2)
+    {
+        if (v1 == v2)
+            return 0;
+        
+        double numerator = mutualWeight(v1, v2);
+        
+        if (numerator == 0)
+            return 0;
+        
+        double denominator = 0;
+        for (V v : g.getNeighbors(v1)) {
+            denominator += mutualWeight(v1, v);
+        }
+        if (denominator == 0)
+            return 0;
+        
+        return numerator / denominator;
+    }
+    
+    /**
+     * Returns the weight of the edge from <code>v1</code> to <code>v2</code>
+     * plus the weight of the edge from <code>v2</code> to <code>v1</code>;
+     * if either edge does not exist, it is treated as an edge with weight 0. 
+     * Undirected edges are treated as two antiparallel directed edges (that
+     * is, if there is one undirected edge with weight <i>w</i> connecting 
+     * <code>v1</code> to <code>v2</code>, the value returned is 2<i>w</i>).
+     * Ignores parallel edges; if there are any such, one is chosen at random.
+     * Throws <code>NullPointerException</code> if either edge is 
+     * present but not assigned a weight by the constructor-specified
+     * <code>NumberEdgeValue</code>.
+     * 
+     * @param v1 the first vertex of the pair whose property is being measured
+     * @param v2 the second vertex of the pair whose property is being measured
+     * @return the weights of the edges {@code<v1, v2>} and {@code <v2, v1>}
+     */
+    protected double mutualWeight(V v1, V v2)
+    {
+        E e12 = g.findEdge(v1,v2);
+        E e21 = g.findEdge(v2,v1);
+        double w12 = (e12 != null ? edge_weight.apply(e12).doubleValue() : 0);
+        double w21 = (e21 != null ? edge_weight.apply(e21).doubleValue() : 0);
+        
+        return w12 + w21;
+    }
+    
+    /**
+     * The marginal strength of v1's relation with contact v2.
+     * Formally:
+     * <pre>
+     * normalized_mutual_weight = mutual_weight(a,b) / (max_c mutual_weight(a,c))
+     * </pre>
+     * Returns 0 if either numerator or denominator is 0, or if <code>v1 == v2</code>.
+     * 
+     * @param v1 the first vertex of the pair whose property is being measured
+     * @param v2 the second vertex of the pair whose property is being measured
+     * @return the marginal strength of v1's relation with v2
+     * 
+     * @see #mutualWeight(Object, Object)
+     */
+    protected double maxScaledMutualEdgeWeight(V v1, V v2)
+    {
+        if (v1 == v2)
+            return 0;
+
+        double numerator = mutualWeight(v1, v2);
+
+        if (numerator == 0)
+            return 0;
+        
+        double denominator = 0;
+        for (V w : g.getNeighbors(v1)) {
+
+            if (v2 != w)
+                denominator = Math.max(numerator, mutualWeight(v1, w));
+        }
+        
+        if (denominator == 0)
+            return 0;
+        
+        return numerator / denominator;
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/TriadicCensus.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/TriadicCensus.java
new file mode 100644
index 0000000..e6732e8
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/TriadicCensus.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.metrics;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.Graph;
+
+
+/**
+ * TriadicCensus is a standard social network tool that counts, for each of the 
+ * different possible configurations of three vertices, the number of times
+ * that that configuration occurs in the given graph.
+ * This may then be compared to the set of expected counts for this particular
+ * graph or to an expected sample. This is often used in p* modeling.
+ * <p>
+ * To use this class, 
+ * <pre>
+ * long[] triad_counts = TriadicCensus(dg);
+ * </pre>
+ * where <code>dg</code> is a <code>DirectedGraph</code>.
+ * ith element of the array (for i in [1,16]) is the number of 
+ * occurrences of the corresponding triad type.
+ * (The 0th element is not meaningful; this array is effectively 1-based.)
+ * To get the name of the ith triad (e.g. "003"), 
+ * look at the global constant array c.TRIAD_NAMES[i]
+ * <p>
+ * Triads are named as 
+ * (number of pairs that are mutually tied)
+ * (number of pairs that are one-way tied)
+ * (number of non-tied pairs)
+ * in the triple. Since there are be only three pairs, there is a finite
+ * set of these possible triads.
+ * <p>
+ * In fact, there are exactly 16, conventionally sorted by the number of 
+ * realized edges in the triad:
+ * <table>
+ * <caption>Descriptions of the different types of triads</caption>
+ * <tr><th>Number</th> <th>Configuration</th> <th>Notes</th></tr>
+ * <tr><td>1</td><td>003</td><td>The empty triad</td></tr>
+ * <tr><td>2</td><td>012</td><td></td></tr>
+ * <tr><td>3</td><td>102</td><td></td></tr>
+ * <tr><td>4</td><td>021D</td><td>"Down": the directed edges point away</td></tr>
+ * <tr><td>5</td><td>021U</td><td>"Up": the directed edges meet</td></tr>
+ * <tr><td>6</td><td>021C</td><td>"Circle": one in, one out</td></tr>
+ * <tr><td>7</td><td>111D</td><td>"Down": 021D but one edge is mutual</td></tr>
+ * <tr><td>8</td><td>111U</td><td>"Up": 021U but one edge is mutual</td></tr>
+ * <tr><td>9</td><td>030T</td><td>"Transitive": two point to the same vertex</td></tr>
+ * <tr><td>10</td><td>030C</td><td>"Circle": A→B→C→A</td></tr>
+ * <tr><td>11</td><td>201</td><td></td></tr>
+ * <tr><td>12</td><td>120D</td><td>"Down": 021D but the third edge is mutual</td></tr>
+ * <tr><td>13</td><td>120U</td><td>"Up": 021U but the third edge is mutual</td></tr>
+ * <tr><td>14</td><td>120C</td><td>"Circle": 021C but the third edge is mutual</td></tr>
+ * <tr><td>15</td><td>210</td><td></td></tr>
+ * <tr><td>16</td><td>300</td><td>The complete</td></tr>
+ * </table>
+ * <p>
+ * This implementation takes O( m ), m is the number of edges in the graph. 
+ * <br>
+ * It is based on 
+ * <a href="http://vlado.fmf.uni-lj.si/pub/networks/doc/triads/triads.pdf">
+ * A subquadratic triad census algorithm for large sparse networks 
+ * with small maximum degree</a>
+ * Vladimir Batagelj and Andrej Mrvar, University of Ljubljana
+ * Published in Social Networks.
+ * @author Danyel Fisher
+ * @author Tom Nelson - converted to jung2
+ *
+ */
+public class TriadicCensus {
+
+	// NOTE THAT THIS RETURNS STANDARD 1-16 COUNT!
+
+	// and their types
+	public static final String[] TRIAD_NAMES = { "N/A", "003", "012", "102", "021D",
+			"021U", "021C", "111D", "111U", "030T", "030C", "201", "120D",
+			"120U", "120C", "210", "300" };
+
+	public static final int MAX_TRIADS = TRIAD_NAMES.length;
+
+	/**
+     * Returns an array whose ith element (for i in [1,16]) is the number of 
+     * occurrences of the corresponding triad type in <code>g</code>.
+     * (The 0th element is not meaningful; this array is effectively 1-based.)
+	 * 
+	 * @param g the graph whose properties are being measured
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+	 * @return an array encoding the number of occurrences of each triad type
+	 */
+    public static <V,E> long[] getCounts(DirectedGraph<V,E> g) {
+        long[] count = new long[MAX_TRIADS];
+
+        List<V> id = new ArrayList<V>(g.getVertices());
+
+		// apply algorithm to each edge, one at at time
+		for (int i_v = 0; i_v < g.getVertexCount(); i_v++) {
+			V v = id.get(i_v);
+			for(V u : g.getNeighbors(v)) {
+				int triType = -1;
+				if (id.indexOf(u) <= i_v)
+					continue;
+				Set<V> neighbors = new HashSet<V>(g.getNeighbors(u));
+				neighbors.addAll(g.getNeighbors(v));
+				neighbors.remove(u);
+				neighbors.remove(v);
+				if (g.isSuccessor(v,u) && g.isSuccessor(u,v)) {
+					triType = 3;
+				} else {
+					triType = 2;
+				}
+				count[triType] += g.getVertexCount() - neighbors.size() - 2;
+				for (V w : neighbors) {
+					if (shouldCount(g, id, u, v, w)) {
+						count [ triType ( triCode(g, u, v, w) ) ] ++;
+					}
+				}
+			}
+		}
+		int sum = 0;
+		for (int i = 2; i <= 16; i++) {
+			sum += count[i];
+		}
+		int n = g.getVertexCount();
+		count[1] = n * (n-1) * (n-2) / 6 - sum;
+		return count;		
+	}
+
+    /**
+	 * This is the core of the technique in the paper. Returns an int from 0 to
+	 * 63 which encodes the presence of all possible links between u, v, and w 
+	 * as bit flags: WU = 32, UW = 16, WV = 8, VW = 4, UV = 2, VU = 1
+     * 
+     * @param g the graph for which the calculation is being made
+     * @param u a vertex in g
+     * @param v a vertex in g
+     * @param w a vertex in g
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @return an int encoding the presence of all links between u, v, and w
+     */
+	public static <V,E> int triCode(Graph<V,E> g, V u, V v, V w) {
+		int i = 0;
+		i += link(g, v, u ) ? 1 : 0;
+		i += link(g, u, v ) ? 2 : 0;
+		i += link(g, v, w ) ? 4 : 0;
+		i += link(g, w, v ) ? 8 : 0;
+		i += link(g, u, w ) ? 16 : 0;
+		i += link(g, w, u ) ? 32 : 0;
+		return i;
+	}
+
+	protected static <V,E> boolean link(Graph<V,E> g, V a, V b) {
+		return g.isPredecessor(b, a);
+	}
+	
+	
+	/**
+	 * @param triCode the code returned by {@code triCode()}
+	 * @return the string code associated with the numeric type
+	 */
+	public static int triType( int triCode ) {
+		return codeToType[ triCode ];
+	}
+
+	/**
+	 * For debugging purposes, this is copied straight out of the paper which
+	 * means that they refer to triad types 1-16.
+	 */
+	protected static final int[] codeToType = { 1, 2, 2, 3, 2, 4, 6, 8, 2, 6, 5, 7, 3, 8,
+			7, 11, 2, 6, 4, 8, 5, 9, 9, 13, 6, 10, 9, 14, 7, 14, 12, 15, 2, 5,
+			6, 7, 6, 9, 10, 14, 4, 9, 9, 12, 8, 13, 14, 15, 3, 7, 8, 11, 7, 12,
+			14, 15, 8, 14, 13, 15, 11, 15, 15, 16 };
+
+	/**
+	 * Return true iff this ordering is canonical and therefore we should build statistics for it.
+	 * 
+	 * @param g the graph whose properties are being examined
+	 * @param id a list of the vertices in g; used to assign an index to each
+	 * @param u a vertex in g
+	 * @param v a vertex in g
+	 * @param w a vertex in g
+     * @param <V> the vertex type
+     * @param <E> the edge type
+	 * @return true if index(u) < index(w), or if index(v) < index(w) < index(u)
+	 *     and v doesn't link to w; false otherwise
+	 */
+	protected static <V,E> boolean shouldCount(Graph<V,E> g, List<V> id, V u, V v, V w) {
+		int i_u = id.indexOf(u);
+		int i_w = id.indexOf(w);
+		if (i_u < i_w)
+			return true;
+		int i_v = id.indexOf(v);
+		if ((i_v < i_w) && (i_w < i_u) && (!g.isNeighbor(w,v)))
+			return true;
+		return false;
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/package.html
new file mode 100644
index 0000000..9d79d2c
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/metrics/package.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Specialized measures for graph properties.  These currently include:
+
+<ul>
+<li><code>StructuralHoles</code>: calculates some of Burt's 'structural holes' 
+measures (e.g. efficiency, hierarchy, constraint). 
+<li><code>TriadicCensus</code>: returns counts for each triad type found in a 
+graph.
+</ul>
+
+</body>
+</html>
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/package.html
new file mode 100644
index 0000000..65edf08
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/package.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Algorithms for graphs and networks.
+
+<p>These algorithms are divided into categories as follows:
+<ul>
+<li><b>blockmodel</b>: dividing graph elements (typically vertices) into 
+equivalence classes, 
+generally by topological properties (e.g. structural equivalence)
+<li><b>cluster</b>: identifying coherent (not necessarily disjoint) groups of elements
+(e.g. weakly connected components, edge betweenness clustering)
+<li><b>filters</b>: removing parts of a graph according to specified criteria
+<li><b>flows</b>: calculating properties relating to network flows 
+(e.g. max flow/min cut)
+<li><b>generators</b>: creating graphs with certain properties
+<li><b>importance (<i>deprecated</i>)</b>: assigning values to vertices/edges 
+based on topological properties
+<li><b>layout</b>: arrangement of graph elements, generally for visualization
+<li><b>metrics</b>: calculating structural properties (triad census, structural 
+holes)
+<li><b>scoring</b>: assigning values (denoting significance, influence, 
+centrality, etc.) to vertices/edges based on topological properties,
+e.g. PageRank, HITS, betweenness centrality (replaces "importance", above)
+<li><b>shortestpath</b>: calculation of shortest paths between vertices
+<li><b>util</b>: low-level utility classes used in a variety of algorithms
+</ul>
+
+</body>
+</html>
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/AbstractIterativeScorer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/AbstractIterativeScorer.java
new file mode 100644
index 0000000..26c89ab
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/AbstractIterativeScorer.java
@@ -0,0 +1,370 @@
+/*
+ * Created on Jul 6, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.scoring.util.DelegateToEdgeTransformer;
+import edu.uci.ics.jung.algorithms.scoring.util.VEPair;
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * An abstract class for algorithms that assign scores to vertices based on iterative methods.
+ * Generally, any (concrete) subclass will function by creating an instance, and then either calling
+ * <code>evaluate</code> (if the user wants to iterate until the algorithms is 'done') or 
+ * repeatedly call <code>step</code> (if the user wants to observe the values at each step).
+ */
+public abstract class AbstractIterativeScorer<V,E,T> implements IterativeContext, VertexScorer<V,T>
+{
+    /**
+     * Maximum number of iterations to use before terminating.  Defaults to 100.
+     */
+    protected int max_iterations;
+    
+    /**
+     * Minimum change from one step to the next; if all changes are ≤ tolerance, 
+     * no further updates will occur.
+     * Defaults to 0.001.
+     */
+    protected double tolerance;
+    
+    /**
+     * The graph on which the calculations are to be made.
+     */
+    protected Hypergraph<V,E> graph;
+    
+    /**
+     * The total number of iterations used so far.
+     */
+    protected int total_iterations;
+    
+    /**
+     * The edge weights used by this algorithm.
+     */
+    protected Function<VEPair<V,E>, ? extends Number> edge_weights;
+    
+    /**
+     * Indicates whether the output and current values are in a 'swapped' state.
+     * Intended for internal use only.
+     */
+    protected boolean output_reversed;
+    
+    /**
+     * The map in which the output values are stored.
+     */
+    private Map<V, T> output;
+    
+    /**
+     * The map in which the current values are stored.
+     */
+    private Map<V, T> current_values;
+    
+    /**
+     * A flag representing whether this instance tolerates disconnected graphs.
+     * Instances that do not accept disconnected graphs may have unexpected behavior
+     * on disconnected graphs; they are not guaranteed to do an explicit check.
+     * Defaults to true.
+     */
+    private boolean accept_disconnected_graph;
+
+
+    protected boolean hyperedges_are_self_loops = false;
+
+    /**
+     * Sets the output value for this vertex.
+     * @param v the vertex whose output value is to be set
+     * @param value the value to set
+     */
+    protected void setOutputValue(V v, T value)
+    {
+        output.put(v, value);
+    }
+    
+    /**
+     * Gets the output value for this vertex.
+     * @param v the vertex whose output value is to be retrieved
+     * @return the output value for this vertex
+     */
+    protected T getOutputValue(V v)
+    {
+        return output.get(v);
+    }
+    
+    /**
+     * Gets the current value for this vertex
+     * @param v the vertex whose current value is to be retrieved
+     * @return the current value for this vertex
+     */
+    protected T getCurrentValue(V v)
+    {
+        return current_values.get(v);
+    }
+    
+    /**
+     * Sets the current value for this vertex.
+     * @param v the vertex whose current value is to be set
+     * @param value the current value to set
+     */
+    protected void setCurrentValue(V v, T value)
+    {
+        current_values.put(v, value);
+    }
+    
+    /**
+     * The largest change seen so far among all vertex scores.
+     */
+    protected double max_delta;
+    
+    /**
+     * Creates an instance for the specified graph and edge weights.
+     * @param g the graph for which the instance is to be created
+     * @param edge_weights the edge weights for this instance
+     */
+    public AbstractIterativeScorer(Hypergraph<V,E> g, 
+    		Function<? super E, ? extends Number> edge_weights)
+    {
+        this.graph = g;
+        this.max_iterations = 100;
+        this.tolerance = 0.001;
+        this.accept_disconnected_graph = true;
+        setEdgeWeights(edge_weights);
+    }
+    
+    /**
+     * Creates an instance for the specified graph <code>g</code>.
+     * NOTE: This constructor does not set the internal 
+     * <code>edge_weights</code> variable.  If this variable is used by 
+     * the subclass which invoked this constructor, it must be initialized
+     * by that subclass.
+     * @param g the graph for which the instance is to be created
+     */
+    public AbstractIterativeScorer(Hypergraph<V,E> g)
+    {
+    	this.graph = g;
+        this.max_iterations = 100;
+        this.tolerance = 0.001;
+        this.accept_disconnected_graph = true;
+    }
+    
+    /**
+     * Initializes the internal state for this instance.
+     */
+    protected void initialize()
+    {
+        this.total_iterations = 0;
+        this.max_delta = Double.MIN_VALUE;
+        this.output_reversed = true;
+        this.current_values = new HashMap<V, T>();
+        this.output = new HashMap<V, T>();
+    }
+    
+    /**
+     * Steps through this scoring algorithm until a termination condition is reached.
+     */
+    public void evaluate()
+    {
+        do
+            step();
+        while (!done());
+    }
+    
+    /**
+     * Returns true if the total number of iterations is greater than or equal to 
+     * <code>max_iterations</code>
+     * or if the maximum value change observed is less than <code>tolerance</code>.
+     */
+    public boolean done()
+    {
+        return total_iterations >= max_iterations || max_delta < tolerance;
+    }
+
+    /**
+     * Performs one step of this algorithm; updates the state (value) for each vertex.
+     */
+    public void step()
+    {
+        swapOutputForCurrent();
+        
+        for (V v : graph.getVertices())
+        {
+            double diff = update(v);
+            updateMaxDelta(v, diff);
+        }
+        total_iterations++;
+        afterStep();
+    }
+
+    /**
+     * 
+     */
+    protected void swapOutputForCurrent()
+    {
+        Map<V, T> tmp = output;
+        output = current_values;
+        current_values = tmp;
+        output_reversed = !output_reversed;
+    }
+
+    /**
+     * Updates the value for <code>v</code>.
+     * @param v the vertex whose value is to be updated
+     * @return the updated value
+     */
+    protected abstract double update(V v);
+
+    protected void updateMaxDelta(V v, double diff)
+    {
+        max_delta = Math.max(max_delta, diff);
+    }
+    
+    protected void afterStep() {}
+    
+    public T getVertexScore(V v)
+    {
+        if (!graph.containsVertex(v))
+            throw new IllegalArgumentException("Vertex " + v + " not an element of this graph");
+        
+        return output.get(v);
+    }
+
+    /**
+     * Returns the maximum number of iterations that this instance will use.
+     * @return the maximum number of iterations that <code>evaluate</code> will use
+     * prior to terminating
+     */
+    public int getMaxIterations()
+    {
+        return max_iterations;
+    }
+
+    /**
+     * Returns the number of iterations that this instance has used so far.
+     * @return the number of iterations that this instance has used so far
+     */
+    public int getIterations()
+    {
+    	return total_iterations;
+    }
+    
+    /**
+     * Sets the maximum number of times that <code>evaluate</code> will call <code>step</code>.
+     * @param max_iterations the maximum 
+     */
+    public void setMaxIterations(int max_iterations)
+    {
+        this.max_iterations = max_iterations;
+    }
+
+    /**
+     * Gets the size of the largest change (difference between the current and previous values)
+     * for any vertex that can be tolerated.  Once all changes are less than this value, 
+     * <code>evaluate</code> will terminate.
+     * @return the size of the largest change that evaluate() will permit
+     */
+    public double getTolerance()
+    {
+        return tolerance;
+    }
+
+    /**
+     * Sets the size of the largest change (difference between the current and previous values)
+     * for any vertex that can be tolerated.
+     * @param tolerance the size of the largest change that evaluate() will permit
+     */
+    public void setTolerance(double tolerance)
+    {
+        this.tolerance = tolerance;
+    }
+    
+    /**
+     * Returns the Function that this instance uses to associate edge weights with each edge.
+     * @return the Function that associates an edge weight with each edge
+     */
+    public Function<VEPair<V,E>, ? extends Number> getEdgeWeights()
+    {
+        return edge_weights;
+    }
+
+    /**
+     * Sets the Function that this instance uses to associate edge weights with each edge
+     * @param edge_weights the Function to use to associate an edge weight with each edge
+     * @see edu.uci.ics.jung.algorithms.scoring.util.UniformDegreeWeight
+     */
+    public void setEdgeWeights(Function<? super E, ? extends Number> edge_weights)
+    {
+        this.edge_weights = new DelegateToEdgeTransformer<V,E>(edge_weights);
+    }
+    
+    /**
+     * Gets the edge weight for <code>e</code> in the context of its (incident) vertex <code>v</code>.
+     * @param v the vertex incident to e as a context in which the edge weight is to be calculated
+     * @param e the edge whose weight is to be returned
+     * @return the edge weight for <code>e</code> in the context of its (incident) vertex <code>v</code>
+     */
+    protected Number getEdgeWeight(V v, E e)
+    {
+        return edge_weights.apply(new VEPair<V,E>(v,e));
+    }
+    
+    /**
+     * Collects the 'potential' from v (its current value) if it has no outgoing edges; this
+     * can then be redistributed among the other vertices as a means of normalization.
+     * @param v the vertex whose potential is being collected
+     */
+    protected void collectDisappearingPotential(V v) {}
+
+    /**
+     * Specifies whether this instance should accept vertices with no outgoing edges.
+     * @param accept true if this instance should accept vertices with no outgoing edges, false otherwise
+     */
+    public void acceptDisconnectedGraph(boolean accept)
+    {
+        this.accept_disconnected_graph = accept;
+    }
+    
+    /**
+     * Returns true if this instance accepts vertices with no outgoing edges, and false otherwise.
+     * @return true if this instance accepts vertices with no outgoing edges, otherwise false
+     */
+    public boolean isDisconnectedGraphOK()
+    {
+        return this.accept_disconnected_graph;
+    }
+    
+    /**
+     * Specifies whether hyperedges are to be treated as self-loops.  If they
+     * are, then potential will flow along a hyperedge a vertex to itself, 
+     * just as it does to all other vertices incident to that hyperedge. 
+     * @param arg if {@code true}, hyperedges are treated as self-loops
+     */
+    public void setHyperedgesAreSelfLoops(boolean arg) 
+    {
+    	this.hyperedges_are_self_loops = arg;
+    }
+
+    /**
+     * Returns the effective number of vertices incident to this edge.  If
+     * the graph is a binary relation or if hyperedges are treated as self-loops,
+     * the value returned is {@code graph.getIncidentCount(e)}; otherwise it is
+     * {@code graph.getIncidentCount(e) - 1}.
+     * @param e the edge whose incident edge count is requested
+     * @return the edge count, adjusted based on how hyperedges are treated
+     */
+    protected int getAdjustedIncidentCount(E e) 
+    {
+        return graph.getIncidentCount(e) - (hyperedges_are_self_loops ? 0 : 1);
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/AbstractIterativeScorerWithPriors.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/AbstractIterativeScorerWithPriors.java
new file mode 100644
index 0000000..35c5b23
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/AbstractIterativeScorerWithPriors.java
@@ -0,0 +1,117 @@
+/*
+ * Created on Jul 14, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * An abstract class for iterative random-walk-based vertex scoring algorithms 
+ * that have a 
+ * fixed probability, for each vertex, of 'jumping' to that vertex at each
+ * step in the algorithm (rather than following a link out of that vertex).
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ * @param <S> the score type
+ */
+public abstract class AbstractIterativeScorerWithPriors<V,E,S> extends
+        AbstractIterativeScorer<V,E,S> implements VertexScorer<V,S>
+{
+    /**
+     * The prior probability of each vertex being visited on a given 
+     * 'jump' (non-link-following) step.
+     */
+    protected Function<? super V,? extends S> vertex_priors;
+
+    /**
+     * The probability of making a 'jump' at each step.
+     */
+    protected double alpha;
+
+    /**
+     * Creates an instance for the specified graph, edge weights, vertex
+     * priors, and jump probability.
+     * @param g the graph whose vertices are to be assigned scores
+     * @param edge_weights the edge weights to use in the score assignment
+     * @param vertex_priors the prior probabilities of each vertex being 'jumped' to
+     * @param alpha the probability of making a 'jump' at each step
+     */
+    public AbstractIterativeScorerWithPriors(Hypergraph<V,E> g,
+            Function<? super E,? extends Number> edge_weights, 
+            Function<? super V,? extends S> vertex_priors, double alpha)
+    {
+        super(g, edge_weights);
+        this.vertex_priors = vertex_priors;
+        this.alpha = alpha;
+        initialize();
+    }
+
+    /**
+     * Creates an instance for the specified graph, vertex priors, and jump
+     * probability, with edge weights specified by the subclass.
+     * @param g the graph whose vertices are to be assigned scores
+     * @param vertex_priors the prior probabilities of each vertex being 'jumped' to
+     * @param alpha the probability of making a 'jump' at each step
+     */
+    public AbstractIterativeScorerWithPriors(Hypergraph<V,E> g, 
+    		Function<V,? extends S> vertex_priors, double alpha)
+    {
+        super(g);
+        this.vertex_priors = vertex_priors;
+        this.alpha = alpha;
+        initialize();
+    }
+
+    /**
+     * Initializes the state of this instance.
+     */
+    @Override
+    public void initialize()
+    {
+        super.initialize();
+        // initialize output values to priors
+        // (output and current are swapped before each step(), so current will
+        // have priors when update()s start happening)
+        for (V v : graph.getVertices())
+            setOutputValue(v, getVertexPrior(v));
+    }
+    
+    /**
+     * Returns the prior probability for <code>v</code>.
+     * @param v the vertex whose prior probability is being queried
+     * @return the prior probability for <code>v</code>
+     */
+    protected S getVertexPrior(V v)
+    {
+        return vertex_priors.apply(v);
+    }
+
+    /**
+     * Returns a Function which maps each vertex to its prior probability.
+     * @return a Function which maps each vertex to its prior probability
+     */
+    public Function<? super V, ? extends S> getVertexPriors()
+    {
+        return vertex_priors;
+    }
+
+    /**
+     * Returns the probability of making a 'jump' (non-link-following step).
+     * @return the probability of making a 'jump' (non-link-following step)
+     */
+    public double getAlpha()
+    {
+        return alpha;
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/BarycenterScorer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/BarycenterScorer.java
new file mode 100644
index 0000000..28a1f1d
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/BarycenterScorer.java
@@ -0,0 +1,55 @@
+/*
+ * Created on Jul 12, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.shortestpath.Distance;
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * Assigns scores to each vertex according to the sum of its distances to all other vertices.
+ */
+public class BarycenterScorer<V,E> extends DistanceCentralityScorer<V, E>
+{
+    /**
+     * Creates an instance with the specified graph and distance metric.
+     * @param graph the input graph
+     * @param distance the distance metric to use
+     */
+    public BarycenterScorer(Hypergraph<V,E> graph, Distance<V> distance)
+    {
+        super(graph, distance, false);
+    }
+    
+    /**
+     * Creates an instance with the specified graph and edge weights.
+     * Will generate a <code>Distance</code> metric internally based on the edge weights. 
+     * @param graph the input graph
+     * @param edge_weights the edge weights to use to calculate vertex/vertex distances
+     */
+    public BarycenterScorer(Hypergraph<V,E> graph, Function<E, ? extends Number> edge_weights)
+    {
+        super(graph, edge_weights, false);
+    }
+
+    /**
+     * Creates an instance with the specified graph.
+     * Will generate a <code>Distance</code> metric internally assuming that the
+     * graph is unweighted. 
+     * @param graph the input graph
+     */
+    public BarycenterScorer(Hypergraph<V,E> graph)
+    {
+        super(graph, false);
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/BetweennessCentrality.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/BetweennessCentrality.java
new file mode 100644
index 0000000..b6162f5
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/BetweennessCentrality.java
@@ -0,0 +1,350 @@
+/**
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Sep 16, 2008
+ * 
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Stack;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.util.MapBinaryHeap;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+
+/**
+ * Computes betweenness centrality for each vertex and edge in the graph.
+ * 
+ * @see "Ulrik Brandes: A Faster Algorithm for Betweenness Centrality. Journal of Mathematical Sociology 25(2):163-177, 2001."
+ */
+public class BetweennessCentrality<V, E> 
+	implements VertexScorer<V, Double>, EdgeScorer<E, Double> 
+{
+	protected Graph<V,E> graph;
+	protected Map<V, Double> vertex_scores;
+	protected Map<E, Double> edge_scores;
+	protected Map<V, BetweennessData> vertex_data;
+		
+	/**
+	 * Calculates betweenness scores based on the all-pairs unweighted shortest paths
+	 * in the graph.
+	 * @param graph the graph for which the scores are to be calculated
+	 */
+	public BetweennessCentrality(Graph<V, E> graph) 
+	{
+		initialize(graph);
+		computeBetweenness(new LinkedList<V>(), Functions.<Integer>constant(1));
+	}
+
+	/**
+	 * Calculates betweenness scores based on the all-pairs weighted shortest paths in the
+	 * graph.
+	 * 
+	 * <p>NOTE: This version of the algorithm may not work correctly on all graphs; we're still
+	 * working out the bugs.  Use at your own risk.
+	 * @param graph the graph for which the scores are to be calculated
+	 * @param edge_weights the edge weights to be used in the path length calculations
+	 */
+	public BetweennessCentrality(Graph<V, E> graph, 
+			Function<? super E, ? extends Number> edge_weights) 
+	{
+		// reject negative-weight edges up front
+		for (E e : graph.getEdges())
+		{
+			double e_weight = edge_weights.apply(e).doubleValue();
+        	if (e_weight < 0)
+        		throw new IllegalArgumentException(String.format(
+        				"Weight for edge '%s' is < 0: %d", e, e_weight)); 
+		}
+			
+		initialize(graph);
+		computeBetweenness(new MapBinaryHeap<V>(new BetweennessComparator()), 
+			edge_weights);
+	}
+
+	protected void initialize(Graph<V,E> graph)
+	{
+		this.graph = graph;
+		this.vertex_scores = new HashMap<V, Double>();
+		this.edge_scores = new HashMap<E, Double>();
+		this.vertex_data = new HashMap<V, BetweennessData>();
+		
+		for (V v : graph.getVertices())
+			this.vertex_scores.put(v, 0.0);
+		
+		for (E e : graph.getEdges())
+			this.edge_scores.put(e, 0.0);
+	}
+	
+	protected void computeBetweenness(Queue<V> queue, 
+			Function<? super E, ? extends Number> edge_weights)
+	{
+		for (V v : graph.getVertices())
+		{
+			// initialize the betweenness data for this new vertex
+			for (V s : graph.getVertices()) 
+				this.vertex_data.put(s, new BetweennessData());
+
+//			if (v.equals(new Integer(0)))
+//				System.out.println("pause");
+			
+            vertex_data.get(v).numSPs = 1;
+            vertex_data.get(v).distance = 0;
+
+            Stack<V> stack = new Stack<V>();
+//            Buffer<V> queue = new UnboundedFifoBuffer<V>();
+//            queue.add(v);
+            queue.offer(v);
+
+            while (!queue.isEmpty()) 
+            {
+//                V w = queue.remove();
+            	V w = queue.poll();
+                stack.push(w);
+            	BetweennessData w_data = vertex_data.get(w);
+                
+                for (E e : graph.getOutEdges(w))
+                {
+                	// TODO (jrtom): change this to getOtherVertices(w, e)
+                	V x = graph.getOpposite(w, e);
+                	if (x.equals(w))
+                		continue;
+                	double wx_weight = edge_weights.apply(e).doubleValue();
+                	
+                	
+//                for(V x : graph.getSuccessors(w)) 
+//                {
+//                	if (x.equals(w))
+//                		continue;
+                	
+                	// FIXME: the other problem is that I need to 
+                	// keep putting the neighbors of things we've just 
+                	// discovered in the queue, if they're undiscovered or
+                	// at greater distance.
+                	
+                	// FIXME: this is the problem, right here, I think: 
+                	// need to update position in queue if distance changes
+                	// (which can only happen with weighted edges).
+                	// for each outgoing edge e from w, get other end x
+                	// if x not already visited (dist x < 0)
+                	//   set x's distance to w's dist + edge weight
+                	//   add x to queue; pri in queue is x's dist
+                	// if w's dist + edge weight < x's dist 
+                	//   update x's dist
+                	//   update x in queue (MapBinaryHeap)
+                	//   clear x's incoming edge list
+                	// if w's dist + edge weight = x's dist
+                	//   add e to x's incoming edge list
+                	
+                	BetweennessData x_data = vertex_data.get(x);
+                	double x_potential_dist = w_data.distance + wx_weight;
+                	
+                    if (x_data.distance < 0) 
+                    {
+//                        queue.add(x);
+//                        vertex_data.get(x).distance = vertex_data.get(w).distance + 1;
+                    	x_data.distance = x_potential_dist;
+                      	queue.offer(x);
+                    }
+                    
+                    // note:
+                    // (1) this can only happen with weighted edges
+                    // (2) x's SP count and incoming edges are updated below 
+                    if (x_data.distance > x_potential_dist)
+                    {
+                    	x_data.distance = x_potential_dist;
+                    	// invalidate previously identified incoming edges
+                    	// (we have a new shortest path distance to x)
+                    	x_data.incomingEdges.clear(); 
+                        // update x's position in queue
+                    	((MapBinaryHeap<V>)queue).update(x);
+                    }
+//                  if (vertex_data.get(x).distance == vertex_data.get(w).distance + 1) 
+                    // 
+//                    if (x_data.distance == x_potential_dist) 
+//                    {
+//                        x_data.numSPs += w_data.numSPs;
+////                        vertex_data.get(x).predecessors.add(w);
+//                        x_data.incomingEdges.add(e);
+//                    }
+                }
+                for (E e: graph.getOutEdges(w))
+                {
+                	V x = graph.getOpposite(w, e);
+                	if (x.equals(w))
+                		continue;
+                	double e_weight = edge_weights.apply(e).doubleValue();
+                	BetweennessData x_data = vertex_data.get(x);
+                	double x_potential_dist = w_data.distance + e_weight;
+                    if (x_data.distance == x_potential_dist) 
+                    {
+                        x_data.numSPs += w_data.numSPs;
+//                        vertex_data.get(x).predecessors.add(w);
+                        x_data.incomingEdges.add(e);
+                    }
+                }
+            }
+    		while (!stack.isEmpty()) 
+    		{
+    		    V x = stack.pop();
+
+//    		    for (V w : vertex_data.get(x).predecessors) 
+    		    for (E e : vertex_data.get(x).incomingEdges)
+    		    {
+    		    	V w = graph.getOpposite(x, e);
+    		        double partialDependency = 
+    		        	vertex_data.get(w).numSPs / vertex_data.get(x).numSPs *
+    		        	(1.0 + vertex_data.get(x).dependency);
+    		        vertex_data.get(w).dependency +=  partialDependency;
+//    		        E w_x = graph.findEdge(w, x);
+//    		        double w_x_score = edge_scores.get(w_x).doubleValue();
+//    		        w_x_score += partialDependency;
+//    		        edge_scores.put(w_x, w_x_score);
+    		        double e_score = edge_scores.get(e).doubleValue();
+    		        edge_scores.put(e, e_score + partialDependency);
+    		    }
+    		    if (!x.equals(v)) 
+    		    {
+    		    	double x_score = vertex_scores.get(x).doubleValue();
+    		    	x_score += vertex_data.get(x).dependency;
+    		    	vertex_scores.put(x, x_score);
+    		    }
+    		}
+        }
+
+        if(graph instanceof UndirectedGraph) 
+        {
+    		for (V v : graph.getVertices()) { 
+    			double v_score = vertex_scores.get(v).doubleValue();
+    			v_score /= 2.0;
+    			vertex_scores.put(v, v_score);
+    		}
+    		for (E e : graph.getEdges()) {
+    			double e_score = edge_scores.get(e).doubleValue();
+    			e_score /= 2.0;
+    			edge_scores.put(e, e_score);
+    		}
+        }
+
+        vertex_data.clear();
+	}
+
+//	protected void computeWeightedBetweenness(Function<E, ? extends Number> edge_weights)
+//	{
+//		for (V v : graph.getVertices())
+//		{
+//			// initialize the betweenness data for this new vertex
+//			for (V s : graph.getVertices()) 
+//				this.vertex_data.put(s, new BetweennessData());
+//            vertex_data.get(v).numSPs = 1;
+//            vertex_data.get(v).distance = 0;
+//
+//            Stack<V> stack = new Stack<V>();
+////            Buffer<V> queue = new UnboundedFifoBuffer<V>();
+//            SortedSet<V> pqueue = new TreeSet<V>(new BetweennessComparator());
+////          queue.add(v);
+//            pqueue.add(v);
+//
+////            while (!queue.isEmpty()) 
+//            while (!pqueue.isEmpty()) 
+//            {
+////              V w = queue.remove();
+//            	V w = pqueue.first();
+//            	pqueue.remove(w);
+//                stack.push(w);
+//
+////                for(V x : graph.getSuccessors(w)) 
+//                for (E e : graph.getOutEdges(w))
+//                {
+//                	// TODO (jrtom): change this to getOtherVertices(w, e)
+//                	V x = graph.getOpposite(w, e);
+//                	if (x.equals(w))
+//                		continue;
+//                	double e_weight = edge_weights.transform(e).doubleValue();
+//                	
+//                    if (vertex_data.get(x).distance < 0) 
+//                    {
+////                        queue.add(x);
+//                    	pqueue.add(v);
+////                        vertex_data.get(x).distance = vertex_data.get(w).distance + 1;
+//                        vertex_data.get(x).distance = 
+//                        	vertex_data.get(w).distance + e_weight;
+//                    }
+//
+////                    if (vertex_data.get(x).distance == vertex_data.get(w).distance + 1) 
+//                    if (vertex_data.get(x).distance == 
+//                    	vertex_data.get(w).distance + e_weight)
+//                    {
+//                        vertex_data.get(x).numSPs += vertex_data.get(w).numSPs;
+//                        vertex_data.get(x).predecessors.add(w);
+//                    }
+//                }
+//            }
+//            updateScores(v, stack);
+//        }
+//
+//        if(graph instanceof UndirectedGraph) 
+//            adjustUndirectedScores();
+//
+//        vertex_data.clear();
+//	}
+	
+	public Double getVertexScore(V v) 
+	{
+		return vertex_scores.get(v);
+	}
+
+	public Double getEdgeScore(E e) 
+	{
+		return edge_scores.get(e);
+	}
+
+    private class BetweennessData 
+    {
+        double distance;
+        double numSPs;
+//        List<V> predecessors;
+        List<E> incomingEdges;
+        double dependency;
+
+        BetweennessData() 
+        {
+            distance = -1;
+            numSPs = 0;
+//            predecessors = new ArrayList<V>();
+            incomingEdges = new ArrayList<E>();
+            dependency = 0;
+        }
+        
+        @Override
+        public String toString()
+        {
+        	return "[d:" + distance + ", sp:" + numSPs + 
+        		", p:" + incomingEdges + ", d:" + dependency + "]\n";
+//        		", p:" + predecessors + ", d:" + dependency + "]\n";
+        }
+    }
+    
+    private class BetweennessComparator implements Comparator<V>
+    {
+		public int compare(V v1, V v2) 
+		{
+			return vertex_data.get(v1).distance > vertex_data.get(v2).distance ? 1 : -1;
+		}
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/ClosenessCentrality.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/ClosenessCentrality.java
new file mode 100644
index 0000000..bfcfe33
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/ClosenessCentrality.java
@@ -0,0 +1,55 @@
+/*
+ * Created on Jul 12, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.shortestpath.Distance;
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * Assigns scores to each vertex based on the mean distance to each other vertex.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class ClosenessCentrality<V,E> extends DistanceCentralityScorer<V,E>
+{
+
+    /**
+     * Creates an instance using the specified vertex/vertex distance metric.
+     * @param graph the input
+     * @param distance the vertex/vertex distance metric.
+     */
+    public ClosenessCentrality(Hypergraph<V,E> graph, Distance<V> distance)
+    {
+        super(graph, distance, true);
+    }
+
+    /**
+     * Creates an instance which measures distance using the specified edge weights.
+     * @param graph the input graph
+     * @param edge_weights the edge weights to be used to determine vertex/vertex distances
+     */
+    public ClosenessCentrality(Hypergraph<V,E> graph, Function<E, ? extends Number> edge_weights)
+    {
+        super(graph, edge_weights, true);
+    }
+
+    /**
+     * Creates an instance which measures distance on the graph without edge weights.
+     * @param graph the graph whose vertices' centrality scores will be calculated
+     */
+    public ClosenessCentrality(Hypergraph<V,E> graph)
+    {
+        super(graph, true);
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/DegreeScorer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/DegreeScorer.java
new file mode 100644
index 0000000..838a5c5
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/DegreeScorer.java
@@ -0,0 +1,45 @@
+/*
+ * Created on Jul 6, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * Assigns a score to each vertex equal to its degree.
+ *
+ * @param <V> the vertex type
+ */
+public class DegreeScorer<V> implements VertexScorer<V,Integer>
+{
+	/**
+	 * The graph for which scores are to be generated.
+	 */
+    protected Hypergraph<V,?> graph;
+    
+    /**
+     * Creates an instance for the specified graph.
+     * @param graph the input graph
+     */
+    public DegreeScorer(Hypergraph<V,?> graph)
+    {
+        this.graph = graph;
+    }
+    
+    /**
+     * Returns the degree of the vertex.
+     * @return the degree of the vertex
+     */
+    public Integer getVertexScore(V v) 
+    {
+        return graph.degree(v); 
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/DistanceCentralityScorer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/DistanceCentralityScorer.java
new file mode 100644
index 0000000..9ac488b
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/DistanceCentralityScorer.java
@@ -0,0 +1,249 @@
+/*
+ * Created on Jul 10, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.shortestpath.DijkstraDistance;
+import edu.uci.ics.jung.algorithms.shortestpath.Distance;
+import edu.uci.ics.jung.algorithms.shortestpath.UnweightedShortestPath;
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * Assigns scores to vertices based on their distances to each other vertex 
+ * in the graph.
+ * 
+ * This class optionally normalizes its results based on the value of its
+ * 'averaging' constructor parameter.  If it is <code>true</code>, 
+ * then the value returned for vertex v is 1 / (_average_ distance from v to all other vertices); 
+ * this is sometimes called <i>closeness centrality</i>.
+ * If it is <code>false</code>, then the value returned is 1 / (_total_ distance from
+ * v to all other vertices); this is sometimes referred to as <i>barycenter centrality</i>.
+ * (If the average/total distance is 0, the value returned is {@code Double.POSITIVE_INFINITY}.)
+ * 
+ * @see BarycenterScorer
+ * @see ClosenessCentrality
+ */
+public class DistanceCentralityScorer<V,E> implements VertexScorer<V, Double>
+{
+    /**
+     * The graph on which the vertex scores are to be calculated.
+     */
+    protected Hypergraph<V, E> graph;
+    
+    /**
+     * The metric to use for specifying the distance between pairs of vertices.
+     */
+    protected Distance<V> distance;
+    
+    /**
+     * The cache for the output results.  Null encodes "not yet calculated",
+     * < 0 encodes "no such distance exists".
+     */
+    protected Map<V, Double> output;
+    
+    /**
+     * Specifies whether the values returned are the sum of the v-distances
+     * or the mean v-distance.
+     */
+    protected boolean averaging;
+    
+    /**
+     * Specifies whether, for a vertex <code>v</code> with missing (null) distances, 
+     * <code>v</code>'s score should ignore the missing values or be set to 'null'.
+     * Defaults to 'true'.
+     */
+    protected boolean ignore_missing;
+
+    /**
+     * Specifies whether the values returned should ignore self-distances 
+     * (distances from <code>v</code> to itself).
+     * Defaults to 'true'.
+     */
+    protected boolean ignore_self_distances;
+    
+    /**
+     * Creates an instance with the specified graph, distance metric, and 
+     * averaging behavior.
+     * 
+     * @param graph     The graph on which the vertex scores are to be calculated.
+     * @param distance  The metric to use for specifying the distance between 
+     * pairs of vertices.
+     * @param averaging Specifies whether the values returned is the sum of all 
+     * v-distances or the mean v-distance.
+     * @param ignore_missing	Specifies whether scores for missing distances 
+     * are to ignore missing distances or be set to null.
+     * @param ignore_self_distances	Specifies whether distances from a vertex
+     * to itself should be included in its score.
+     */
+    public DistanceCentralityScorer(Hypergraph<V,E> graph, Distance<V> distance, 
+    		boolean averaging, boolean ignore_missing, 
+    		boolean ignore_self_distances)
+    {
+        this.graph = graph;
+        this.distance = distance;
+        this.averaging = averaging;
+        this.ignore_missing = ignore_missing;
+        this.ignore_self_distances = ignore_self_distances;
+        this.output = new HashMap<V, Double>();
+    }
+
+    /**
+     * Equivalent to <code>this(graph, distance, averaging, true, true)</code>.
+     * 
+     * @param graph     The graph on which the vertex scores are to be calculated.
+     * @param distance  The metric to use for specifying the distance between 
+     * pairs of vertices.
+     * @param averaging Specifies whether the values returned is the sum of all 
+     * v-distances or the mean v-distance.
+     */
+    public DistanceCentralityScorer(Hypergraph<V,E> graph, Distance<V> distance, 
+    		boolean averaging)
+    {
+    	this(graph, distance, averaging, true, true);
+    }
+    
+    /**
+     * Creates an instance with the specified graph and averaging behavior
+     * whose vertex distances are calculated based on the specified edge
+     * weights.  
+     * 
+     * @param graph         The graph on which the vertex scores are to be 
+     * calculated.
+     * @param edge_weights  The edge weights to use for specifying the distance 
+     * between pairs of vertices.
+     * @param averaging     Specifies whether the values returned is the sum of 
+     * all v-distances or the mean v-distance.
+     * @param ignore_missing	Specifies whether scores for missing distances 
+     * are to ignore missing distances or be set to null.
+     * @param ignore_self_distances	Specifies whether distances from a vertex
+     * to itself should be included in its score.
+     */
+    public DistanceCentralityScorer(Hypergraph<V,E> graph, 
+            Function<E, ? extends Number> edge_weights, boolean averaging,
+            boolean ignore_missing, boolean ignore_self_distances)
+    {
+        this(graph, new DijkstraDistance<V,E>(graph, edge_weights), averaging,
+        	ignore_missing, ignore_self_distances);
+    }
+    
+    /**
+     * Equivalent to <code>this(graph, edge_weights, averaging, true, true)</code>.
+     * @param graph         The graph on which the vertex scores are to be 
+     * calculated.
+     * @param edge_weights  The edge weights to use for specifying the distance 
+     * between pairs of vertices.
+     * @param averaging     Specifies whether the values returned is the sum of 
+     * all v-distances or the mean v-distance.
+     */
+    public DistanceCentralityScorer(Hypergraph<V,E> graph, 
+            Function<E, ? extends Number> edge_weights, boolean averaging)
+    {
+        this(graph, new DijkstraDistance<V,E>(graph, edge_weights), averaging,
+        	true, true);
+    }
+    
+    /**
+     * Creates an instance with the specified graph and averaging behavior
+     * whose vertex distances are calculated on the unweighted graph.  
+     * 
+     * @param graph         The graph on which the vertex scores are to be 
+     * calculated.
+     * @param averaging     Specifies whether the values returned is the sum of 
+     * all v-distances or the mean v-distance.
+     * @param ignore_missing	Specifies whether scores for missing distances 
+     * are to ignore missing distances or be set to null.
+     * @param ignore_self_distances	Specifies whether distances from a vertex
+     * to itself should be included in its score.
+     */
+    public DistanceCentralityScorer(Hypergraph<V,E> graph, boolean averaging,
+            boolean ignore_missing, boolean ignore_self_distances)
+    {
+        this(graph, new UnweightedShortestPath<V,E>(graph), averaging, 
+        	ignore_missing, ignore_self_distances);
+    }
+
+    /**
+     * Equivalent to <code>this(graph, averaging, true, true)</code>.
+     * @param graph         The graph on which the vertex scores are to be 
+     * calculated.
+     * @param averaging     Specifies whether the values returned is the sum of 
+     * all v-distances or the mean v-distance.
+     */
+    public DistanceCentralityScorer(Hypergraph<V,E> graph, boolean averaging)
+    {
+        this(graph, new UnweightedShortestPath<V,E>(graph), averaging, true, true);
+    }
+
+	/**
+	 * Calculates the score for the specified vertex.  Returns {@code null} if 
+	 * there are missing distances and such are not ignored by this instance.
+	 */
+	public Double getVertexScore(V v) 
+	{
+	    Double value = output.get(v);
+	    if (value != null)
+	    {
+	        if (value < 0)
+	            return null;
+	        return value;
+	    }
+	    
+	    Map<V, Number> v_distances = new HashMap<V, Number>(distance.getDistanceMap(v));
+	    if (ignore_self_distances)
+	        v_distances.remove(v);
+	    
+		// if we don't ignore missing distances and there aren't enough
+		// distances, output null (shortcut)
+		if (!ignore_missing)
+		{
+			int num_dests = graph.getVertexCount() - 
+			    (ignore_self_distances ? 1 : 0);
+			if (v_distances.size() != num_dests) 
+			{
+				output.put(v, -1.0);
+				return null;
+			}
+		}		
+		
+		Double sum = 0.0;
+		for (V w : graph.getVertices())
+		{
+			if (w.equals(v) && ignore_self_distances)
+				continue;
+			Number w_distance = v_distances.get(w);
+			if (w_distance == null)
+				if (ignore_missing)
+					continue;
+				else
+				{
+					output.put(v, -1.0);
+					return null;
+				}
+			else
+				sum += w_distance.doubleValue();
+		}
+		value = sum;
+		if (averaging)
+		    value /= v_distances.size();
+		
+		double score = value == 0 ? 
+			Double.POSITIVE_INFINITY : 
+			1.0 / value;
+	    output.put(v, score);
+		   
+		return score;
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/EdgeScorer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/EdgeScorer.java
new file mode 100644
index 0000000..1a4fcf3
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/EdgeScorer.java
@@ -0,0 +1,28 @@
+/*
+ * Created on Jul 6, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+
+/**
+ * An interface for algorithms that assign scores to edges.
+ *
+ * @param <E> the edge type
+ * @param <S> the score type
+ */
+public interface EdgeScorer<E, S>
+{
+    /**
+     * @param e the edge whose score is requested
+     * @return the algorithm's score for this edge
+     */
+    public S getEdgeScore(E e);
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/EigenvectorCentrality.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/EigenvectorCentrality.java
new file mode 100644
index 0000000..ff303af
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/EigenvectorCentrality.java
@@ -0,0 +1,52 @@
+/*
+ * Created on Jul 12, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * Calculates eigenvector centrality for each vertex in the graph.
+ * The 'eigenvector centrality' for a vertex is defined as the fraction of
+ * time that a random walk(er) will spend at that vertex over an infinite
+ * time horizon.
+ * Assumes that the graph is strongly connected.
+ */
+public class EigenvectorCentrality<V,E> extends PageRank<V,E>
+{
+    /**
+     * Creates an instance with the specified graph and edge weights.
+     * The outgoing edge weights for each edge must sum to 1.
+     * (See <code>UniformDegreeWeight</code> for one way to handle this for
+     * undirected graphs.)
+     * @param graph the graph for which the centrality is to be calculated
+     * @param edge_weights the edge weights 
+     */
+    public EigenvectorCentrality(Hypergraph<V,E> graph, 
+    		Function<E, ? extends Number> edge_weights)
+    {
+        super(graph, edge_weights, 0);
+        acceptDisconnectedGraph(false);
+    }
+
+    /**
+     * Creates an instance with the specified graph and default edge weights.
+     * (Default edge weights: <code>UniformDegreeWeight</code>.)
+     * @param graph the graph for which the centrality is to be calculated.
+     */
+    public EigenvectorCentrality(Hypergraph<V,E> graph)
+    {
+        super(graph, 0);
+        acceptDisconnectedGraph(false);
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/HITS.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/HITS.java
new file mode 100644
index 0000000..957f3b4
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/HITS.java
@@ -0,0 +1,147 @@
+/*
+ * Created on Jul 15, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.scoring.util.ScoringUtils;
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * Assigns hub and authority scores to each vertex depending on the topology of
+ * the network.  The essential idea is that a vertex is a hub to the extent 
+ * that it links to authoritative vertices, and is an authority to the extent
+ * that it links to 'hub' vertices.
+ * 
+ * <p>The classic HITS algorithm essentially proceeds as follows:
+ * <pre>
+ * assign equal initial hub and authority values to each vertex
+ * repeat
+ *   for each vertex w:
+ *     w.hub = sum over successors x of x.authority
+ *     w.authority = sum over predecessors v of v.hub
+ *   normalize hub and authority scores so that the sum of the squares of each = 1
+ * until scores converge
+ * </pre>
+ * 
+ * HITS is somewhat different from random walk/eigenvector-based algorithms 
+ * such as PageRank in that: 
+ * <ul>
+ * <li>there are two mutually recursive scores being calculated, rather than 
+ * a single value
+ * <li>the edge weights are effectively all 1, i.e., they can't be interpreted
+ * as transition probabilities.  This means that the more inlinks and outlinks
+ * that a vertex has, the better, since adding an inlink (or outlink) does
+ * not dilute the influence of the other inlinks (or outlinks) as in 
+ * random walk-based algorithms.
+ * <li>the scores cannot be interpreted as posterior probabilities (due to the different
+ * normalization)
+ * </ul>
+ * 
+ * This implementation has the classic behavior by default.  However, it has
+ * been generalized somewhat so that it can act in a more "PageRank-like" fashion:
+ * <ul>
+ * <li>this implementation has an optional 'random jump probability' parameter analogous
+ * to the 'alpha' parameter used by PageRank.  Varying this value between 0 and 1
+ * allows the user to vary between the classic HITS behavior and one in which the
+ * scores are smoothed to a uniform distribution.
+ * The default value for this parameter is 0 (no random jumps possible).
+ * <li>the edge weights can be set to anything the user likes, and in 
+ * particular they can be set up (e.g. using <code>UniformDegreeWeight</code>)
+ * so that the weights of the relevant edges incident to a vertex sum to 1.
+ * <li>The vertex score normalization has been factored into its own method
+ * so that it can be overridden by a subclass.  Thus, for example, 
+ * since the vertices' values are set to sum to 1 initially, if the weights of the
+ * relevant edges incident to a vertex sum to 1, then the vertices' values
+ * will continue to sum to 1 if the "sum-of-squares" normalization code
+ * is overridden to a no-op.  (Other normalization methods may also be employed.)
+ * </ul>
+ * 
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ * 
+ * @see "'Authoritative sources in a hyperlinked environment' by Jon Kleinberg, 1997"
+ */
+public class HITS<V,E> extends HITSWithPriors<V,E>
+{
+
+    /**
+     * Creates an instance for the specified graph, edge weights, and alpha
+     * (random jump probability) parameter.
+     * @param g the input graph
+     * @param edge_weights the weights to use for each edge
+     * @param alpha the probability of a hub giving some authority to all vertices,
+     * and of an authority increasing the score of all hubs (not just those connected
+     * via links)
+     */
+    public HITS(Graph<V,E> g, Function<E, Double> edge_weights, double alpha)
+    {
+        super(g, edge_weights, ScoringUtils.getHITSUniformRootPrior(g.getVertices()), alpha);
+    }
+
+    /**
+     * Creates an instance for the specified graph and alpha (random jump probability)
+     * parameter.  The edge weights are all set to 1.
+     * @param g the input graph
+     * @param alpha the probability of a hub giving some authority to all vertices,
+     * and of an authority increasing the score of all hubs (not just those connected
+     * via links)
+     */
+    public HITS(Graph<V,E> g, double alpha)
+    {
+        super(g, ScoringUtils.getHITSUniformRootPrior(g.getVertices()), alpha);
+    }
+
+    /**
+     * Creates an instance for the specified graph.  The edge weights are all set to 1
+     * and alpha is set to 0.
+     * @param g the input graph
+     */
+    public HITS(Graph<V,E> g)
+    {
+        this(g, 0.0);
+    }
+    
+
+    /**
+     * Maintains hub and authority score information for a vertex.
+     */
+    public static class Scores
+    {
+    	/**
+    	 * The hub score for a vertex.
+    	 */
+    	public double hub;
+    	
+    	/**
+    	 * The authority score for a vertex.
+    	 */
+    	public double authority;
+    	
+    	/**
+    	 * Creates an instance with the specified hub and authority score.
+    	 * @param hub the hub score
+    	 * @param authority the authority score
+    	 */
+    	public Scores(double hub, double authority)
+    	{
+    		this.hub = hub;
+    		this.authority = authority;
+    	}
+    	
+    	@Override
+        public String toString()
+    	{
+    		return String.format("[h:%.4f,a:%.4f]", this.hub, this.authority);
+    	}
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/HITSWithPriors.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/HITSWithPriors.java
new file mode 100644
index 0000000..b7cc64a
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/HITSWithPriors.java
@@ -0,0 +1,199 @@
+/*
+ * Created on Jul 14, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * A generalization of HITS that permits non-uniformly-distributed random jumps.
+ * The 'vertex_priors' (that is, prior probabilities for each vertex) may be
+ * thought of as the fraction of the total 'potential' (hub or authority score)
+ * that is assigned to that vertex out of the portion that is assigned according
+ * to random jumps.
+ * 
+ * @see "Algorithms for Estimating Relative Importance in Graphs by Scott White and Padhraic Smyth, 2003"
+ */
+public class HITSWithPriors<V, E> 
+	extends AbstractIterativeScorerWithPriors<V,E,HITS.Scores>
+{
+    /**
+     * The sum of the potential, at each step, associated with vertices with no outedges (authority)
+     * or no inedges (hub).
+     */
+    protected HITS.Scores disappearing_potential;
+
+    /**
+     * Creates an instance for the specified graph, edge weights, vertex prior probabilities,
+     * and random jump probability (alpha).
+     * @param g the input graph
+     * @param edge_weights the edge weights 
+     * @param vertex_priors the prior probability for each vertex
+     * @param alpha the probability of a random jump at each step
+     */
+    public HITSWithPriors(Hypergraph<V,E> g,
+            Function<E, ? extends Number> edge_weights,
+            Function<V, HITS.Scores> vertex_priors, double alpha)
+    {
+        super(g, edge_weights, vertex_priors, alpha);
+        disappearing_potential = new HITS.Scores(0,0);
+    }
+
+    /**
+     * Creates an instance for the specified graph, vertex priors, and random
+     * jump probability (alpha).  The edge weights default to 1.0.
+     * @param g the input graph
+     * @param vertex_priors the prior probability for each vertex
+     * @param alpha the probability of a random jump at each step
+     */
+    public HITSWithPriors(Hypergraph<V,E> g, 
+          Function<V, HITS.Scores> vertex_priors, double alpha)
+    {
+    	super(g, Functions.constant(1.0), vertex_priors, alpha);
+        disappearing_potential = new HITS.Scores(0,0);
+    }
+
+    /**
+     * Updates the value for this vertex.
+     */
+    @Override
+    protected double update(V v)
+    {
+        collectDisappearingPotential(v);
+        
+        double v_auth = 0;
+        for (E e : graph.getInEdges(v))
+        {
+        	int incident_count = getAdjustedIncidentCount(e);
+        	for (V w : graph.getIncidentVertices(e)) 
+        	{
+        		if (!w.equals(v) || hyperedges_are_self_loops) 
+        			v_auth += (getCurrentValue(w).hub * 
+        					getEdgeWeight(w,e).doubleValue() / incident_count);
+        	}
+//            V w = graph.getOpposite(v, e);
+//            auth += (getCurrentValue(w).hub * getEdgeWeight(w, e).doubleValue());
+        }
+        
+        double v_hub = 0;
+        for (E e : graph.getOutEdges(v))
+        {
+        	int incident_count = getAdjustedIncidentCount(e);
+        	for (V w : graph.getIncidentVertices(e)) 
+        	{
+        		if (!w.equals(v) || hyperedges_are_self_loops) 
+        			v_hub += (getCurrentValue(w).authority * 
+        					getEdgeWeight(w,e).doubleValue() / incident_count);
+        	}
+//            V x = graph.getOpposite(v,e);
+//            hub += (getCurrentValue(x).authority * getEdgeWeight(x, e).doubleValue()); 
+        }
+        
+        // modify total_input according to alpha
+        if (alpha > 0) 
+        {
+	        v_auth = v_auth * (1 - alpha) + getVertexPrior(v).authority * alpha;
+	        v_hub = v_hub * (1 - alpha) + getVertexPrior(v).hub * alpha;
+        }
+        setOutputValue(v, new HITS.Scores(v_hub, v_auth));
+
+        return Math.max(Math.abs(getCurrentValue(v).hub - v_hub), 
+                        Math.abs(getCurrentValue(v).authority - v_auth));
+    }
+
+    /**
+     * Code which is executed after each step.  In this case, deals with the
+     * 'disappearing potential', normalizes the scores, and then calls
+     * <code>super.afterStep()</code>.
+     * @see #collectDisappearingPotential(Object)
+     */
+    @Override
+    protected void afterStep()
+    {
+        if (disappearing_potential.hub > 0 || disappearing_potential.authority > 0)
+        {
+            for (V v : graph.getVertices())
+            {
+                double new_hub = getOutputValue(v).hub + 
+                    (1 - alpha) * (disappearing_potential.hub * getVertexPrior(v).hub);
+                double new_auth = getOutputValue(v).authority + 
+                    (1 - alpha) * (disappearing_potential.authority * getVertexPrior(v).authority);
+                setOutputValue(v, new HITS.Scores(new_hub, new_auth));
+            }
+            disappearing_potential.hub = 0;
+            disappearing_potential.authority = 0;
+        }
+        
+    	normalizeScores();
+    	
+        super.afterStep();
+    }
+
+	/**
+	 * Normalizes scores so that sum of their squares = 1.
+	 * This method may be overridden so as to yield different 
+	 * normalizations.
+	 */
+	protected void normalizeScores() {
+    	double hub_ssum = 0;
+    	double auth_ssum = 0;
+    	for (V v : graph.getVertices())
+    	{
+    		double hub_val = getOutputValue(v).hub;
+    		double auth_val = getOutputValue(v).authority;
+    		hub_ssum += (hub_val * hub_val);
+    		auth_ssum += (auth_val * auth_val);
+    	}
+
+    	hub_ssum = Math.sqrt(hub_ssum);
+    	auth_ssum = Math.sqrt(auth_ssum);
+    	
+    	for (V v : graph.getVertices())
+    	{
+    		HITS.Scores values = getOutputValue(v);
+    		setOutputValue(v, new HITS.Scores(
+    				values.hub / hub_ssum,
+    				values.authority / auth_ssum));
+    	}
+	}
+    
+	/**
+	 * Collects the "disappearing potential" associated with vertices that have either 
+	 * no incoming edges, no outgoing edges, or both.  Vertices that have no incoming edges
+	 * do not directly contribute to the hub scores of other vertices; similarly, vertices
+	 * that have no outgoing edges do not directly contribute to the authority scores of
+	 * other vertices.  These values are collected at each step and then distributed across all vertices
+	 * as a part of the normalization process.  (This process is not required for, and does
+	 * not affect, the 'sum-of-squares'-style normalization.) 
+	 */
+    @Override
+    protected void collectDisappearingPotential(V v)
+    {
+        if (graph.outDegree(v) == 0)
+        {
+            if (isDisconnectedGraphOK())
+                disappearing_potential.hub += getCurrentValue(v).authority;
+            else
+                throw new IllegalArgumentException("Outdegree of " + v + " must be > 0");
+        }
+        if (graph.inDegree(v) == 0)
+        {
+            if (isDisconnectedGraphOK())
+                disappearing_potential.authority += getCurrentValue(v).hub;
+            else
+                throw new IllegalArgumentException("Indegree of " + v + " must be > 0");
+        }
+    }
+
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/KStepMarkov.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/KStepMarkov.java
new file mode 100644
index 0000000..e591c57
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/KStepMarkov.java
@@ -0,0 +1,157 @@
+/**
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Aug 22, 2008
+ * 
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.scoring.util.ScoringUtils;
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * A special case of {@code PageRankWithPriors} in which the final scores
+ * represent a probability distribution over position assuming a random (Markovian)
+ * walk of exactly k steps, based on the initial distribution specified by the priors.
+ * 
+ * <p><b>NOTE</b>: The version of {@code KStepMarkov} in {@code algorithms.importance}
+ * (and in JUNG 1.x) is believed to be incorrect: rather than returning 
+ * a score which represents a probability distribution over position assuming
+ * a k-step random walk, it returns a score which represents the sum over all steps
+ * of the probability for each step.  If you want that behavior, set the 
+ * 'cumulative' flag as follows <i>before calling {@code evaluate()}</i>:
+ * <pre>
+ *     KStepMarkov ksm = new KStepMarkov(...);
+ *     ksm.setCumulative(true);
+ *     ksm.evaluate();
+ * </pre>
+ * 
+ * By default, the 'cumulative' flag is set to false.
+ * 
+ * NOTE: THIS CLASS IS NOT YET COMPLETE.  USE AT YOUR OWN RISK.  (The original behavior
+ * is captured by the version still available in {@code algorithms.importance}.)
+ * 
+ * @see "Algorithms for Estimating Relative Importance in Graphs by Scott White and Padhraic Smyth, 2003"
+ * @see PageRank
+ * @see PageRankWithPriors
+ */
+public class KStepMarkov<V,E> extends PageRankWithPriors<V,E> 
+{
+	private boolean cumulative;
+	
+	/**
+	 * Creates an instance based on the specified graph, edge weights, vertex
+	 * priors (initial scores), and number of steps to take.
+	 * @param graph the input graph
+	 * @param edge_weights the edge weights (transition probabilities)
+	 * @param vertex_priors the initial probability distribution (score assignment)
+	 * @param steps the number of times that {@code step()} will be called by {@code evaluate}
+	 */
+	public KStepMarkov(Hypergraph<V,E> graph, Function<E, ? extends Number> edge_weights, 
+					   Function<V, Double> vertex_priors, int steps)
+	{
+		super(graph, edge_weights, vertex_priors, 0);
+		initialize(steps);
+	}
+	
+	/**
+	 * Creates an instance based on the specified graph, vertex
+	 * priors (initial scores), and number of steps to take.  The edge
+	 * weights (transition probabilities) are set to default values (a uniform
+	 * distribution over all outgoing edges).
+	 * @param graph the input graph
+	 * @param vertex_priors the initial probability distribution (score assignment)
+	 * @param steps the number of times that {@code step()} will be called by {@code evaluate}
+	 */
+	public KStepMarkov(Hypergraph<V,E> graph, Function<V, Double> vertex_priors, int steps)
+	{
+		super(graph, vertex_priors, 0);
+		initialize(steps);
+	}
+	
+	/**
+	 * Creates an instance based on the specified graph and number of steps to 
+	 * take.  The edge weights (transition probabilities) and vertex initial scores
+	 * (prior probabilities) are set to default values (a uniform
+	 * distribution over all outgoing edges, and a uniform distribution over
+	 * all vertices, respectively).
+	 * @param graph the input graph
+	 * @param steps the number of times that {@code step()} will be called by {@code evaluate}
+	 */
+	public KStepMarkov(Hypergraph<V,E> graph, int steps)
+	{
+		super(graph, ScoringUtils.getUniformRootPrior(graph.getVertices()), 0);
+		initialize(steps);
+	}
+	
+	private void initialize(int steps)
+	{
+		this.acceptDisconnectedGraph(false);
+		
+		if (steps <= 0)
+			throw new IllegalArgumentException("Number of steps must be > 0");
+		
+		this.max_iterations = steps;
+		this.tolerance = -1.0;
+		
+		this.cumulative = false;
+	}
+
+	/**
+	 * Specifies whether this instance should assign a score to each vertex
+	 * based on the sum over all steps of the probability for each step.
+	 * See the class-level documentation for details.
+	 * @param cumulative true if this instance should assign a cumulative score to each vertex
+	 */
+	public void setCumulative(boolean cumulative)
+	{
+		this.cumulative = cumulative;
+	}
+	
+    /**
+     * Updates the value for this vertex.  Called by <code>step()</code>.
+     */
+    @Override
+    public double update(V v)
+    {
+    	if (!cumulative)
+    		return super.update(v);
+    	
+        collectDisappearingPotential(v);
+        
+        double v_input = 0;
+        for (E e : graph.getInEdges(v))
+        {
+        	// For graphs, the code below is equivalent to 
+//          V w = graph.getOpposite(v, e);
+//          total_input += (getCurrentValue(w) * getEdgeWeight(w,e).doubleValue());
+        	// For hypergraphs, this divides the potential coming from w 
+        	// by the number of vertices in the connecting edge e.
+        	int incident_count = getAdjustedIncidentCount(e);
+        	for (V w : graph.getIncidentVertices(e)) 
+        	{
+        		if (!w.equals(v) || hyperedges_are_self_loops) 
+        			v_input += (getCurrentValue(w) * 
+        					getEdgeWeight(w,e).doubleValue() / incident_count);
+        	}
+        }
+        
+        // modify total_input according to alpha
+        double new_value = alpha > 0 ? 
+        		v_input * (1 - alpha) + getVertexPrior(v) * alpha :
+        		v_input;
+        setOutputValue(v, new_value + getCurrentValue(v));
+
+        // FIXME: DO WE NEED TO CHANGE HOW DISAPPEARING IS COUNTED?  NORMALIZE?
+        
+        return Math.abs(getCurrentValue(v) - new_value);
+    }
+
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/PageRank.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/PageRank.java
new file mode 100644
index 0000000..baefab9
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/PageRank.java
@@ -0,0 +1,70 @@
+/*
+ * Created on Jul 12, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.scoring.util.ScoringUtils;
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * Assigns scores to each vertex according to the PageRank algorithm.  
+ * 
+ * <p>PageRank is an eigenvector-based algorithm.  The score for a given vertex may be thought of
+ * as the fraction of time spent 'visiting' that vertex (measured over all time) 
+ * in a random walk over the vertices (following outgoing edges from each vertex).  
+ * PageRank modifies this random walk by adding to the model a probability (specified as 'alpha' 
+ * in the constructor) of jumping to any vertex.  If alpha is 0, this is equivalent to the
+ * eigenvector centrality algorithm; if alpha is 1, all vertices will receive the same score
+ * (1/|V|).  Thus, alpha acts as a sort of score smoothing parameter.
+ * 
+ * <p>The original algorithm assumed that, for a given vertex, the probability of following any 
+ * outgoing edge was the same; this is the default if edge weights are not specified.  
+ * This implementation generalizes the original by permitting 
+ * the user to specify edge weights; in order to maintain the original semantics, however,
+ * the weights on the outgoing edges for a given vertex must represent transition probabilities; 
+ * that is, they must sum to 1.
+ * 
+ * <p>If a vertex has no outgoing edges, then the probability of taking a random jump from that
+ * vertex is (by default) effectively 1.  If the user wishes to instead throw an exception when this happens,
+ * call <code>acceptDisconnectedGraph(false)</code> on this instance.
+ * 
+ * <p>Typical values for alpha (according to the original paper) are in the range [0.1, 0.2]
+ * but may be any value between 0 and 1 inclusive.
+ * 
+ * @see "The Anatomy of a Large-Scale Hypertextual Web Search Engine by L. Page and S. Brin, 1999"
+ */
+public class PageRank<V,E> extends PageRankWithPriors<V,E>
+{
+
+    /**
+     * Creates an instance for the specified graph, edge weights, and random jump probability.
+     * @param graph the input graph
+     * @param edge_weight the edge weights (transition probabilities)
+     * @param alpha the probability of taking a random jump to an arbitrary vertex
+     */
+    public PageRank(Hypergraph<V,E> graph, Function<E, ? extends Number> edge_weight, double alpha)
+    {
+        super(graph, edge_weight, ScoringUtils.getUniformRootPrior(graph.getVertices()), alpha);
+    }
+
+    /**
+     * Creates an instance for the specified graph and random jump probability; the probability
+     * of following any outgoing edge from a given vertex is the same.
+     * @param graph the input graph
+     * @param alpha the probability of taking a random jump to an arbitrary vertex
+     */
+    public PageRank(Hypergraph<V,E> graph, double alpha)
+    {
+        super(graph, ScoringUtils.getUniformRootPrior(graph.getVertices()), alpha);
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/PageRankWithPriors.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/PageRankWithPriors.java
new file mode 100644
index 0000000..fa870a3
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/PageRankWithPriors.java
@@ -0,0 +1,142 @@
+/*
+ * Created on Jul 6, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.scoring.util.UniformDegreeWeight;
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * A generalization of PageRank that permits non-uniformly-distributed random jumps.
+ * The 'vertex_priors' (that is, prior probabilities for each vertex) may be
+ * thought of as the fraction of the total 'potential' that is assigned to that 
+ * vertex at each step out of the portion that is assigned according
+ * to random jumps (this portion is specified by 'alpha').
+ * 
+ * @see "Algorithms for Estimating Relative Importance in Graphs by Scott White and Padhraic Smyth, 2003"
+ * @see PageRank
+ */
+public class PageRankWithPriors<V, E> 
+	extends AbstractIterativeScorerWithPriors<V,E,Double>
+{
+    /**
+     * Maintains the amount of potential associated with vertices with no out-edges.
+     */
+    protected double disappearing_potential = 0.0;
+    
+    /**
+     * Creates an instance with the specified graph, edge weights, vertex priors, and 
+     * 'random jump' probability (alpha).
+     * @param graph the input graph
+     * @param edge_weights the edge weights, denoting transition probabilities from source to destination
+     * @param vertex_priors the prior probabilities for each vertex
+     * @param alpha the probability of executing a 'random jump' at each step
+     */
+    public PageRankWithPriors(Hypergraph<V,E> graph, 
+    		Function<E, ? extends Number> edge_weights, 
+            Function<V, Double> vertex_priors, double alpha)
+    {
+        super(graph, edge_weights, vertex_priors, alpha);
+    }
+    
+    /**
+     * Creates an instance with the specified graph, vertex priors, and 
+     * 'random jump' probability (alpha).  The outgoing edge weights for each
+     * vertex will be equal and sum to 1.
+     * @param graph the input graph
+     * @param vertex_priors the prior probabilities for each vertex
+     * @param alpha the probability of executing a 'random jump' at each step
+     */
+    public PageRankWithPriors(Hypergraph<V,E> graph, 
+    		Function<V, Double> vertex_priors, double alpha)
+    {
+        super(graph, vertex_priors, alpha);
+        this.edge_weights = new UniformDegreeWeight<V,E>(graph);
+    }
+    
+    /**
+     * Updates the value for this vertex.  Called by <code>step()</code>.
+     */
+    @Override
+    public double update(V v)
+    {
+        collectDisappearingPotential(v);
+        
+        double v_input = 0;
+        for (E e : graph.getInEdges(v))
+        {
+        	// For graphs, the code below is equivalent to 
+//          V w = graph.getOpposite(v, e);
+//          total_input += (getCurrentValue(w) * getEdgeWeight(w,e).doubleValue());
+        	// For hypergraphs, this divides the potential coming from w 
+        	// by the number of vertices in the connecting edge e.
+        	int incident_count = getAdjustedIncidentCount(e);
+        	for (V w : graph.getIncidentVertices(e)) 
+        	{
+        		if (!w.equals(v) || hyperedges_are_self_loops) 
+        			v_input += (getCurrentValue(w) * 
+        					getEdgeWeight(w,e).doubleValue() / incident_count);
+        	}
+        }
+        
+        // modify total_input according to alpha
+        double new_value = alpha > 0 ? 
+        		v_input * (1 - alpha) + getVertexPrior(v) * alpha :
+        		v_input;
+        setOutputValue(v, new_value);
+        
+        return Math.abs(getCurrentValue(v) - new_value);
+    }
+
+    /**
+     * Cleans up after each step.  In this case that involves allocating the disappearing
+     * potential (thus maintaining normalization of the scores) according to the vertex 
+     * probability priors, and then calling 
+     * <code>super.afterStep</code>.
+     */
+    @Override
+    protected void afterStep()
+    {
+        // distribute disappearing potential according to priors
+        if (disappearing_potential > 0)
+        {
+            for (V v : graph.getVertices())
+            {
+                setOutputValue(v, getOutputValue(v) + 
+                        (1 - alpha) * (disappearing_potential * getVertexPrior(v)));
+            }
+            disappearing_potential = 0;
+        }
+        
+        super.afterStep();
+    }
+    
+    /**
+     * Collects the "disappearing potential" associated with vertices that have 
+     * no outgoing edges.  Vertices that have no outgoing edges do not directly 
+     * contribute to the scores of other vertices.  These values are collected 
+     * at each step and then distributed across all vertices
+     * as a part of the normalization process.
+    */
+    @Override
+    protected void collectDisappearingPotential(V v)
+    {
+        if (graph.outDegree(v) == 0)
+        {
+            if (isDisconnectedGraphOK())
+                disappearing_potential += getCurrentValue(v);
+            else
+                throw new IllegalArgumentException("Outdegree of " + v + " must be > 0");
+        }
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/VertexScorer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/VertexScorer.java
new file mode 100644
index 0000000..b18b4a0
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/VertexScorer.java
@@ -0,0 +1,28 @@
+/*
+ * Created on Jul 6, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+
+/**
+ * An interface for algorithms that assign scores to vertices.
+ *
+ * @param <V> the vertex type
+ * @param <S> the score type
+ */
+public interface VertexScorer<V, S>
+{
+    /**
+     * @param v the vertex whose score is requested
+     * @return the algorithm's score for this vertex
+     */
+    public S getVertexScore(V v);
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/VoltageScorer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/VoltageScorer.java
new file mode 100644
index 0000000..f312b43
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/VoltageScorer.java
@@ -0,0 +1,250 @@
+/*
+ * Created on Jul 15, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.scoring.util.UniformDegreeWeight;
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * Assigns scores to vertices according to their 'voltage' in an approximate 
+ * solution to the Kirchoff equations.  This is accomplished by tying "source"
+ * vertices to specified positive voltages, "sink" vertices to 0 V, and 
+ * iteratively updating the voltage of each other vertex to the (weighted) 
+ * average of the voltages of its neighbors.
+ * 
+ * <p>The resultant voltages will all be in the range <code>[0, max]</code>
+ * where <code>max</code> is the largest voltage of any source vertex (in the
+ * absence of negative source voltages; see below).
+ * 
+ * <p>A few notes about this algorithm's interpretation of the graph data: 
+ * <ul>
+ * <li>Higher edge weights are interpreted as indicative of greater 
+ * influence/effect than lower edge weights.  
+ * <li>Negative edge weights (and negative "source" voltages) invalidate
+ * the interpretation of the resultant values as voltages.  However, this 
+ * algorithm will not reject graphs with negative edge weights or source voltages.
+ * <li>Parallel edges are equivalent to a single edge whose weight is the 
+ * sum of the weights on the parallel edges.
+ * <li>Current flows along undirected edges in both directions, 
+ * but only flows along directed edges in the direction of the edge.
+ * </ul>
+ *  
+ */
+public class VoltageScorer<V, E> extends AbstractIterativeScorer<V, E, Double>
+        implements VertexScorer<V, Double>
+{
+    protected Map<V, ? extends Number> source_voltages;
+    protected Collection<V> sinks;
+    
+    /**
+     * Creates an instance with the specified graph, edge weights, source voltages,
+     * and sinks.
+     * @param g the input graph
+     * @param edge_weights the edge weights, representing conductivity
+     * @param source_voltages the (fixed) voltage for each source
+     * @param sinks the vertices whose voltages are tied to 0
+     */
+    public VoltageScorer(Hypergraph<V, E> g, Function<? super E, ? extends Number> edge_weights, 
+            Map<V, ? extends Number> source_voltages, Collection<V> sinks)
+    {
+        super(g, edge_weights);
+        this.source_voltages = source_voltages;
+        this.sinks = sinks;
+        initialize();
+    }
+
+    /**
+     * Creates an instance with the specified graph, edge weights, source vertices
+     * (each of whose 'voltages' are tied to 1), and sinks.
+     * @param g the input graph
+     * @param edge_weights the edge weights, representing conductivity
+     * @param sources the vertices whose voltages are tied to 1
+     * @param sinks the vertices whose voltages are tied to 0
+     */
+    public VoltageScorer(Hypergraph<V, E> g, Function<? super E, ? extends Number> edge_weights, 
+            Collection<V> sources, Collection<V> sinks)
+    {
+        super(g, edge_weights);
+        
+        Map<V, Double> unit_voltages = new HashMap<V, Double>();
+        for(V v : sources) 
+            unit_voltages.put(v, new Double(1.0));
+        this.source_voltages = unit_voltages;
+        this.sinks = sinks;
+        initialize();
+    }
+
+    /**
+     * Creates an instance with the specified graph, source vertices
+     * (each of whose 'voltages' are tied to 1), and sinks.
+     * The outgoing edges for each vertex are assigned 
+     * weights that sum to 1.
+     * @param g the input graph
+     * @param sources the vertices whose voltages are tied to 1
+     * @param sinks the vertices whose voltages are tied to 0
+     */
+    public VoltageScorer(Hypergraph<V, E> g, Collection<V> sources, Collection<V> sinks)
+    {
+        super(g);
+        
+        Map<V, Double> unit_voltages = new HashMap<V, Double>();
+        for(V v : sources) 
+            unit_voltages.put(v, new Double(1.0));
+        this.source_voltages = unit_voltages;
+        this.sinks = sinks;
+        initialize();
+    }
+    
+    /**
+     * Creates an instance with the specified graph, source voltages,
+     * and sinks.  The outgoing edges for each vertex are assigned 
+     * weights that sum to 1.
+     * @param g the input graph
+     * @param source_voltages the (fixed) voltage for each source
+     * @param sinks the vertices whose voltages are tied to 0
+     */
+    public VoltageScorer(Hypergraph<V, E> g, Map<V, ? extends Number> source_voltages, 
+    		Collection<V> sinks)
+    {
+        super(g);
+        this.source_voltages = source_voltages;
+        this.sinks = sinks;
+        this.edge_weights = new UniformDegreeWeight<V,E>(g);
+        initialize();
+    }
+    
+    /**
+     * Creates an instance with the specified graph, edge weights, source, and
+     * sink.  The source vertex voltage is tied to 1.
+     * @param g the input graph
+     * @param edge_weights the edge weights, representing conductivity
+     * @param source the vertex whose voltage is tied to 1
+     * @param sink the vertex whose voltage is tied to 0
+     */
+    public VoltageScorer(Hypergraph<V,E> g, Function<? super E, ? extends Number> edge_weights, 
+    		V source, V sink)
+    {
+        this(g, edge_weights, Collections.singletonMap(source, 1.0), Collections.singletonList(sink));
+        initialize();
+    }
+
+    /**
+     * Creates an instance with the specified graph, edge weights, source, and
+     * sink.  The source vertex voltage is tied to 1.
+     * The outgoing edges for each vertex are assigned 
+     * weights that sum to 1.
+     * @param g the input graph
+     * @param source the vertex whose voltage is tied to 1
+     * @param sink the vertex whose voltage is tied to 0
+     */
+    public VoltageScorer(Hypergraph<V,E> g, V source, V sink)
+    {
+        this(g, Collections.singletonMap(source, 1.0), Collections.singletonList(sink));
+        initialize();
+    }
+
+    
+    /**
+     * Initializes the state of this instance.
+     */
+    @Override
+    public void initialize()
+    {
+        super.initialize();
+        
+        // sanity check
+        if (source_voltages.isEmpty() || sinks.isEmpty())
+            throw new IllegalArgumentException("Both sources and sinks (grounds) must be defined");
+        
+        if (source_voltages.size() + sinks.size() > graph.getVertexCount())
+            throw new IllegalArgumentException("Source/sink sets overlap, or contain vertices not in graph");
+        
+        for (Map.Entry<V, ? extends Number> entry : source_voltages.entrySet())
+        {
+            V v = entry.getKey();
+            if (sinks.contains(v))
+                throw new IllegalArgumentException("Vertex " + v + " is incorrectly specified as both source and sink");
+            double value = entry.getValue().doubleValue();
+            if (value <= 0)
+                throw new IllegalArgumentException("Source vertex " + v + " has negative voltage");
+        }
+        
+        // set up initial voltages
+        for (V v : graph.getVertices())
+        {
+            if (source_voltages.containsKey(v))
+                setOutputValue(v, source_voltages.get(v).doubleValue());
+            else
+                setOutputValue(v, 0.0);
+        }
+    }
+    
+    /**
+     * @see edu.uci.ics.jung.algorithms.scoring.AbstractIterativeScorer#update(Object)
+     */
+    @Override
+    public double update(V v)
+    {
+        // if it's a voltage source or sink, we're done
+        Number source_volts = source_voltages.get(v);
+        if (source_volts != null) 
+        {
+            setOutputValue(v, source_volts.doubleValue());
+            return 0.0;
+        }
+        if (sinks.contains(v))
+        {
+            setOutputValue(v, 0.0);
+            return 0.0;
+        }
+        
+        Collection<E> edges = graph.getInEdges(v);
+        double voltage_sum = 0;
+        double weight_sum = 0;
+        for (E e: edges)
+        {
+        	int incident_count = getAdjustedIncidentCount(e);
+        	for (V w : graph.getIncidentVertices(e)) 
+        	{
+        		if (!w.equals(v) || hyperedges_are_self_loops) 
+        		{
+        			double weight = getEdgeWeight(w,e).doubleValue() / incident_count;
+        			voltage_sum += getCurrentValue(w).doubleValue() * weight;
+        			weight_sum += weight;
+        		}
+        	}
+//            V w = graph.getOpposite(v, e);
+//            double weight = getEdgeWeight(w,e).doubleValue();
+//            voltage_sum += getCurrentValue(w).doubleValue() * weight;
+//            weight_sum += weight;
+        }
+
+        // if either is 0, new value is 0
+        if (voltage_sum == 0 || weight_sum == 0)
+        {
+            setOutputValue(v, 0.0);
+            return getCurrentValue(v).doubleValue();
+        }
+        
+        setOutputValue(v, voltage_sum / weight_sum);
+        return Math.abs(getCurrentValue(v).doubleValue() - voltage_sum / weight_sum);
+    }
+
+}
+
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/package.html
new file mode 100644
index 0000000..45ca7af
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/package.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Mechanisms for assigning values (denoting significance, influence, centrality, etc.)
+to graph elements based on topological properties.  These include:
+
+<ul>
+<li><code>BarycenterScorer</code>: assigns a score to each vertex according to 
+the sum of the distances to all other vertices
+<li><code>ClosenessCentrality</code>: assigns a score to each vertex based on 
+the mean distance to each other vertex
+<li><code>DegreeScorer</code>: assigns a score to each vertex based on its degree
+<li><code>EigenvectorCentrality</code>: assigns vertex scores based on 
+long-term probabilities of random walks passing through the vertex at time t
+<li><code>PageRank</code>: like <code>EigenvectorCentrality</code>, but with 
+a constant probability of the
+random walk restarting at a uniform-randomly chosen vertex
+<li><code>PageRankWithPriors</code>: like <code>PageRank</code>, but with a 
+constant probability of the random
+walk restarting at a vertex drawn from an arbitrary distribution
+<li><code>HITS</code>: assigns hubs-and-authorities scores to vertices based on 
+complementary random walk processes
+<li><code>HITSWithPriors</code>: analogous to <code>HITS</code> 
+(see <code>PageRankWithPriors</code>)
+<li><code>VoltageScorer</code>: assigns scores to vertices based on simulated 
+current flow along edges
+</ul>
+
+</body>
+</html>
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/DelegateToEdgeTransformer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/DelegateToEdgeTransformer.java
new file mode 100644
index 0000000..b31db4c
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/DelegateToEdgeTransformer.java
@@ -0,0 +1,48 @@
+/*
+ * Created on Jul 11, 2008
+ *
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring.util;
+
+import com.google.common.base.Function;
+
+/**
+ * A {@code Transformer<VEPair,Number} that delegates its operation to a
+ * {@code Transformer<E,Number>}.  Mainly useful for technical reasons inside 
+ * AbstractIterativeScorer; in essence it allows the edge weight instance 
+ * variable to be of type <code>VEPair,W</code> even if the edge weight 
+ * <code>Transformer</code> only operates on edges.
+ */
+public class DelegateToEdgeTransformer<V,E> implements
+        Function<VEPair<V,E>,Number>
+{
+	/**
+	 * The Function to which this instance delegates its function.
+	 */
+    protected Function<? super E,? extends Number> delegate;
+    
+    /**
+     * Creates an instance with the specified delegate Function.
+     * @param delegate the Function to which this instance will delegate
+     */
+    public DelegateToEdgeTransformer(Function<? super E,? extends Number> delegate)
+    {
+        this.delegate = delegate;
+    }
+    
+    /**
+     * @see Function#apply(Object)
+     */
+    public Number apply(VEPair<V,E> arg0)
+    {
+        return delegate.apply(arg0.getE());
+    }
+
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/ScoringUtils.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/ScoringUtils.java
new file mode 100644
index 0000000..ed8c33e
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/ScoringUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Created on Jul 12, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring.util;
+
+import java.util.Collection;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.scoring.HITS;
+
+/**
+ * Methods for assigning values (to be interpreted as prior probabilities) to vertices in the context
+ * of random-walk-based scoring algorithms.
+ */
+public class ScoringUtils
+{
+    /**
+     * Assigns a probability of 1/<code>roots.size()</code> to each of the elements of <code>roots</code>.
+     * @param <V> the vertex type
+     * @param roots the vertices to be assigned nonzero prior probabilities
+     * @return a Function assigning a uniform prior to each element in {@code roots} 
+     */
+    public static <V> Function<V, Double> getUniformRootPrior(Collection<V> roots)
+    {
+        final Collection<V> inner_roots = roots;
+        Function<V, Double> distribution = new Function<V, Double>()
+        {
+            public Double apply(V input)
+            {
+                if (inner_roots.contains(input))
+                    return new Double(1.0 / inner_roots.size());
+                else
+                    return 0.0;
+            }
+        };
+        
+        return distribution;
+    }
+    
+    /**
+     * Returns a Function that hub and authority values of 1/<code>roots.size()</code> to each 
+     * element of <code>roots</code>.
+     * @param <V> the vertex type
+     * @param roots the vertices to be assigned nonzero scores
+     * @return a Function that assigns uniform prior hub/authority probabilities to each root
+     */
+    public static <V> Function<V, HITS.Scores> getHITSUniformRootPrior(Collection<V> roots)
+    {
+        final Collection<V> inner_roots = roots;
+        Function<V, HITS.Scores> distribution = 
+        	new Function<V, HITS.Scores>()
+        {
+            public HITS.Scores apply(V input)
+            {
+                if (inner_roots.contains(input))
+                    return new HITS.Scores(1.0 / inner_roots.size(), 1.0 / inner_roots.size());
+                else
+                    return new HITS.Scores(0.0, 0.0);
+            }
+        };
+        return distribution;
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/UniformDegreeWeight.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/UniformDegreeWeight.java
new file mode 100644
index 0000000..fd1f527
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/UniformDegreeWeight.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Jul 14, 2008
+ * 
+ */
+package edu.uci.ics.jung.algorithms.scoring.util;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+/**
+ * An edge weight function that assigns weights as uniform
+ * transition probabilities.
+ * For undirected edges, returns 1/degree(v) (where 'v' is the
+ * vertex in the VEPair.
+ * For directed edges, returns 1/outdegree(source(e)) (where 'e'
+ * is the edge in the VEPair).
+ * Throws an <code>IllegalArgumentException</code> if the input 
+ * edge is neither EdgeType.UNDIRECTED nor EdgeType.DIRECTED.
+ *
+ */
+public class UniformDegreeWeight<V, E> implements
+		Function<VEPair<V, E>, Double> 
+{
+    private Hypergraph<V, E> graph;
+    
+    /**
+     * @param graph the graph for which an instance is being created
+     */
+    public UniformDegreeWeight(Hypergraph<V, E> graph)
+    {
+        this.graph = graph;
+    }
+
+	public Double apply(VEPair<V, E> ve_pair) 
+	{
+		E e = ve_pair.getE();
+		V v = ve_pair.getV();
+		EdgeType edge_type = graph.getEdgeType(e);
+		if (edge_type == EdgeType.UNDIRECTED)
+			return 1.0 / graph.degree(v);
+		if (edge_type == EdgeType.DIRECTED)
+			return 1.0 / graph.outDegree(graph.getSource(e));
+		throw new IllegalArgumentException("can't handle edge type: " + edge_type);
+	}
+
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/UniformInOut.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/UniformInOut.java
new file mode 100644
index 0000000..9201c60
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/UniformInOut.java
@@ -0,0 +1,53 @@
+/*
+ * Created on Jul 11, 2008
+ *
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring.util;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+/**
+ * Assigns weights to directed edges (the edge of the vertex/edge pair) depending on 
+ * whether the vertex is the edge's source or its destination.
+ * If the vertex v is the edge's source, assigns 1/outdegree(v).
+ * Otherwise, assigns 1/indegree(w).
+ * Throws <code>IllegalArgumentException</code> if the edge is not directed.
+ */
+public class UniformInOut<V,E> implements Function<VEPair<V,E>, Double>
+{
+	/**
+	 * The graph for which the edge weights are defined.
+	 */
+    protected Graph<V,E> graph;
+    
+    /**
+     * Creates an instance for the specified graph.
+     * @param graph the graph for which the edge weights will be defined
+     */
+    public UniformInOut(Graph<V,E> graph)
+    {
+        this.graph = graph;
+    }
+    
+    public Double apply(VEPair<V,E> ve_pair)
+    {
+    	V v = ve_pair.getV();
+    	E e = ve_pair.getE();
+    	if (graph.getEdgeType(e) != EdgeType.DIRECTED)
+    		throw new IllegalArgumentException("This Function only" +
+    				" operates on directed edges");
+    	return 1.0 / (graph.isSource(v, e) ? 
+    			graph.outDegree(v) : 
+    			graph.inDegree(v));
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/VEPair.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/VEPair.java
new file mode 100644
index 0000000..b1f2ee2
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/VEPair.java
@@ -0,0 +1,56 @@
+/*
+ * Created on Jul 8, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring.util;
+
+/**
+ * Convenience class for associating a vertex and an edge.  Used, for example,
+ * in contexts in which it is necessary to know the origin for an edge traversal
+ * (that is, the direction in which an (undirected) edge is being traversed).
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+public class VEPair<V, E>
+{
+    private V v;
+    private E e;
+    
+    /**
+     * Creates an instance with the specified vertex and edge
+     * @param v the vertex to add
+     * @param e the edge to add
+     */
+    public VEPair(V v, E e)
+    {
+        if (v == null || e == null)
+            throw new IllegalArgumentException("elements must be non-null");
+        
+        this.v = v;
+        this.e = e;
+    }
+    
+    /**
+     * @return the vertex of this pair
+     */
+    public V getV()
+    {
+        return v;
+    }
+    
+    /**
+     * @return the edge of this pair
+     */
+    public E getE()
+    {
+        return e;
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/VertexScoreTransformer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/VertexScoreTransformer.java
new file mode 100644
index 0000000..c6bc995
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/VertexScoreTransformer.java
@@ -0,0 +1,46 @@
+/*
+ * Created on Jul 18, 2008
+ *
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.scoring.util;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.scoring.VertexScorer;
+
+/**
+ * A Function convenience wrapper around VertexScorer.
+ */
+public class VertexScoreTransformer<V, S> implements Function<V, S>
+{
+    /**
+     * The VertexScorer instance that provides the values returned by <code>transform</code>.
+     */
+    protected VertexScorer<V,S> vs;
+
+    /**
+     * Creates an instance based on the specified VertexScorer.
+     * @param vs the VertexScorer which will retrieve the score for each vertex
+     */
+    public VertexScoreTransformer(VertexScorer<V,S> vs)
+    {
+        this.vs = vs;
+    }
+
+    /**
+     * @param v the vertex whose score is being returned
+     * @return the score for this vertex.
+     */
+    public S apply(V v)
+    {
+        return vs.getVertexScore(v);
+    }
+
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/package.html
new file mode 100644
index 0000000..c100e61
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/scoring/util/package.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Utility functions for assigning scores to graph elements.  These include:
+<ul>
+<li><code>EdgeWeight</code>: interface for classes that associate numeric values 
+with edges
+<li><code>ScoringUtils</code>: methods for calculating transition probabilities
+for random-walk-based algorithms.
+<li><code>UniformOut</code>: an edge weight function that assigns weights as uniform
+transition probabilities to all outgoing edges of a vertex.
+<li><code>UniformIncident</code>: an edge weight function that assigns
+weights as uniform transition probabilities to all incident edges of a
+vertex (useful for undirected graphs). 
+<li><code>VEPair</code>: analogous to <code>Pair</code> but specifically 
+containing an associated vertex and edge.
+<li><code>VertexEdgeWeight</code>: a subtype of <code>EdgeWeight</code> that 
+assigns edge weights with respect to a specified 'source' vertex. 
+</ul>
+
+</body>
+</html>
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/BFSDistanceLabeler.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/BFSDistanceLabeler.java
new file mode 100644
index 0000000..dbfe750
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/BFSDistanceLabeler.java
@@ -0,0 +1,170 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * Labels each node in the graph according to the BFS distance from the start node(s). If nodes are unreachable, then
+ * they are assigned a distance of -1.
+ * All nodes traversed at step k are marked as predecessors of their successors traversed at step k+1.
+ * <p>
+ * Running time is: O(m)
+ * @author Scott White
+ */
+public class BFSDistanceLabeler<V, E> {
+
+    private Map<V, Number> distanceDecorator = new HashMap<V,Number>();
+    private List<V> mCurrentList;
+    private Set<V> mUnvisitedVertices;
+    private List<V> mVerticesInOrderVisited;
+    private Map<V,HashSet<V>> mPredecessorMap;
+
+	/**
+	 * Creates a new BFS labeler for the specified graph and root set
+	 * The distances are stored in the corresponding Vertex objects and are of type MutableInteger
+	 */
+	public BFSDistanceLabeler() {
+		mPredecessorMap = new HashMap<V,HashSet<V>>();
+	}
+
+    /**
+     * Returns the list of vertices visited in order of traversal
+     * @return the list of vertices
+     */
+    public List<V> getVerticesInOrderVisited() {
+        return mVerticesInOrderVisited;
+    }
+
+    /**
+     * Returns the set of all vertices that were not visited
+     * @return the list of unvisited vertices
+     */
+    public Set<V> getUnvisitedVertices() {
+        return mUnvisitedVertices;
+    }
+
+    /**
+     * Given a vertex, returns the shortest distance from any node in the root set to v
+     * @param g the graph in which the distances are to be measured
+     * @param v the vertex whose distance is to be retrieved
+     * @return the shortest distance from any node in the root set to v
+     */
+    public int getDistance(Hypergraph<V,E> g, V v) {
+        if (!g.getVertices().contains(v)) {
+            throw new IllegalArgumentException("Vertex is not contained in the graph.");
+        }
+
+        return distanceDecorator.get(v).intValue();
+    }
+
+    /**
+     * Returns set of predecessors of the given vertex
+     * @param v the vertex whose predecessors are to be retrieved
+     * @return the set of predecessors
+     */
+    public Set<V> getPredecessors(V v) {
+        return mPredecessorMap.get(v);
+    }
+
+    protected void initialize(Hypergraph<V,E> g, Set<V> rootSet) {
+        mVerticesInOrderVisited = new ArrayList<V>();
+        mUnvisitedVertices = new HashSet<V>();
+        for(V currentVertex : g.getVertices()) {
+            mUnvisitedVertices.add(currentVertex);
+            mPredecessorMap.put(currentVertex,new HashSet<V>());
+        }
+
+        mCurrentList = new ArrayList<V>();
+        for(V v : rootSet) {
+            distanceDecorator.put(v, new Integer(0));
+            mCurrentList.add(v);
+            mUnvisitedVertices.remove(v);
+            mVerticesInOrderVisited.add(v);
+        }
+    }
+
+    private void addPredecessor(V predecessor,V sucessor) {
+        HashSet<V> predecessors = mPredecessorMap.get(sucessor);
+        predecessors.add(predecessor);
+    }
+
+    /**
+     * Computes the distances of all the node from the starting root nodes. If there is more than one root node
+     * the minimum distance from each root node is used as the designated distance to a given node. Also keeps track
+     * of the predecessors of each node traversed as well as the order of nodes traversed.
+     * @param graph the graph to label
+     * @param rootSet the set of starting vertices to traverse from
+     */
+    public void labelDistances(Hypergraph<V,E> graph, Set<V> rootSet) {
+
+        initialize(graph,rootSet);
+
+        int distance = 1;
+        while (true) {
+            List<V> newList = new ArrayList<V>();
+            for(V currentVertex : mCurrentList) {
+            	if(graph.containsVertex(currentVertex)) {
+            		for(V next : graph.getSuccessors(currentVertex)) {
+            			visitNewVertex(currentVertex,next, distance, newList);
+            		}
+            	}
+            }
+            if (newList.size() == 0) break;
+            mCurrentList = newList;
+            distance++;
+        }
+
+        for(V v : mUnvisitedVertices) {
+            distanceDecorator.put(v,new Integer(-1));
+        }
+    }
+
+    /**
+     * Computes the distances of all the node from the specified root node. Also keeps track
+     * of the predecessors of each node traversed as well as the order of nodes traversed.
+     *  @param graph the graph to label
+     * @param root the single starting vertex to traverse from
+     */
+    public void labelDistances(Hypergraph<V,E> graph, V root) {
+        labelDistances(graph, Collections.singleton(root));
+    }
+
+    private void visitNewVertex(V predecessor, V neighbor, int distance, List<V> newList) {
+        if (mUnvisitedVertices.contains(neighbor)) {
+            distanceDecorator.put(neighbor, new Integer(distance));
+            newList.add(neighbor);
+            mVerticesInOrderVisited.add(neighbor);
+            mUnvisitedVertices.remove(neighbor);
+        }
+        int predecessorDistance = distanceDecorator.get(predecessor).intValue();
+        int successorDistance = distanceDecorator.get(neighbor).intValue();
+        if (predecessorDistance < successorDistance) {
+            addPredecessor(predecessor,neighbor);
+        }
+    }
+
+    /**
+     * Must be called after {@code labelDistances} in order to contain valid data.
+     * @return a map from vertices to minimum distances from the original source(s)
+     */
+    public Map<V, Number> getDistanceDecorator() {
+        return distanceDecorator;
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/DijkstraDistance.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/DijkstraDistance.java
new file mode 100644
index 0000000..01509d2
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/DijkstraDistance.java
@@ -0,0 +1,545 @@
+/*
+ * Created on Jul 9, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.util.BasicMapEntry;
+import edu.uci.ics.jung.algorithms.util.MapBinaryHeap;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * <p>Calculates distances in a specified graph, using  
+ * Dijkstra's single-source-shortest-path algorithm.  All edge weights
+ * in the graph must be nonnegative; if any edge with negative weight is 
+ * found in the course of calculating distances, an 
+ * <code>IllegalArgumentException</code> will be thrown.
+ * (Note: this exception will only be thrown when such an edge would be
+ * used to update a given tentative distance;
+ * the algorithm does not check for negative-weight edges "up front".)
+ * 
+ * <p>Distances and partial results are optionally cached (by this instance)
+ * for later reference.  Thus, if the 10 closest vertices to a specified source 
+ * vertex are known, calculating the 20 closest vertices does not require 
+ * starting Dijkstra's algorithm over from scratch.
+ * 
+ * <p>Distances are stored as double-precision values.  
+ * If a vertex is not reachable from the specified source vertex, no 
+ * distance is stored.  <b>This is new behavior with version 1.4</b>;
+ * the previous behavior was to store a value of 
+ * <code>Double.POSITIVE_INFINITY</code>.  This change gives the algorithm
+ * an approximate complexity of O(kD log k), where k is either the number of
+ * requested targets or the number of reachable vertices (whichever is smaller),
+ * and D is the average degree of a vertex.
+ * 
+ * <p> The elements in the maps returned by <code>getDistanceMap</code> 
+ * are ordered (that is, returned 
+ * by the iterator) by nondecreasing distance from <code>source</code>.
+ * 
+ * <p>Users are cautioned that distances calculated should be assumed to
+ * be invalidated by changes to the graph, and should invoke <code>reset()</code>
+ * when appropriate so that the distances can be recalculated.
+ * 
+ * @author Joshua O'Madadhain
+ * @author Tom Nelson converted to jung2
+ */
+public class DijkstraDistance<V,E> implements Distance<V>
+{
+    protected Hypergraph<V,E> g;
+    protected Function<? super E,? extends Number> nev;
+    protected Map<V,SourceData> sourceMap;   // a map of source vertices to an instance of SourceData
+    protected boolean cached;
+    protected double max_distance;
+    protected int max_targets;
+    
+    /**
+     * <p>Creates an instance of <code>DijkstraShortestPath</code> for 
+     * the specified graph and the specified method of extracting weights 
+     * from edges, which caches results locally if and only if 
+     * <code>cached</code> is <code>true</code>.
+     * 
+     * @param g     the graph on which distances will be calculated
+     * @param nev   the class responsible for returning weights for edges
+     * @param cached    specifies whether the results are to be cached
+     */
+    public DijkstraDistance(Hypergraph<V,E> g, Function<? super E,? extends Number> nev, boolean cached) {
+        this.g = g;
+        this.nev = nev;
+        this.sourceMap = new HashMap<V,SourceData>();
+        this.cached = cached;
+        this.max_distance = Double.POSITIVE_INFINITY;
+        this.max_targets = Integer.MAX_VALUE;
+    }
+    
+    /**
+     * <p>Creates an instance of <code>DijkstraShortestPath</code> for 
+     * the specified graph and the specified method of extracting weights 
+     * from edges, which caches results locally.
+     * 
+     * @param g     the graph on which distances will be calculated
+     * @param nev   the class responsible for returning weights for edges
+     */
+    public DijkstraDistance(Hypergraph<V,E> g, Function<? super E,? extends Number> nev) {
+        this(g, nev, true);
+    }
+    
+    /**
+     * <p>Creates an instance of <code>DijkstraShortestPath</code> for 
+     * the specified unweighted graph (that is, all weights 1) which
+     * caches results locally.
+     * 
+     * @param g     the graph on which distances will be calculated
+     */ 
+    public DijkstraDistance(Graph<V,E> g) {
+        this(g, Functions.constant(1), true);
+    }
+
+    /**
+     * <p>Creates an instance of <code>DijkstraShortestPath</code> for 
+     * the specified unweighted graph (that is, all weights 1) which
+     * caches results locally.
+     * 
+     * @param g     the graph on which distances will be calculated
+     * @param cached    specifies whether the results are to be cached
+     */ 
+    public DijkstraDistance(Graph<V,E> g, boolean cached) {
+        this(g, Functions.constant(1), cached);
+    }
+    
+    /**
+     * Implements Dijkstra's single-source shortest-path algorithm for
+     * weighted graphs.  Uses a <code>MapBinaryHeap</code> as the priority queue, 
+     * which gives this algorithm a time complexity of O(m lg n) (m = # of edges, n = 
+     * # of vertices).
+     * This algorithm will terminate when any of the following have occurred (in order
+     * of priority):
+     * <ul>
+     * <li> the distance to the specified target (if any) has been found
+     * <li> no more vertices are reachable 
+     * <li> the specified # of distances have been found, or the maximum distance 
+     * desired has been exceeded
+     * <li> all distances have been found
+     * </ul>
+     * 
+     * @param source    the vertex from which distances are to be measured
+     * @param numDests  the number of distances to measure
+     * @param targets   the set of vertices to which distances are to be measured
+     * @return a mapping from vertex to the shortest distance from the source to each target
+     */
+    protected LinkedHashMap<V,Number> singleSourceShortestPath(V source, Collection<V> targets, int numDests)
+    {
+        SourceData sd = getSourceData(source);
+
+        Set<V> to_get = new HashSet<V>();
+        if (targets != null) {
+            to_get.addAll(targets);
+            Set<V> existing_dists = sd.distances.keySet();
+            for(V o : targets) {
+                if (existing_dists.contains(o))
+                    to_get.remove(o);
+            }
+        }
+        
+        // if we've exceeded the max distance or max # of distances we're willing to calculate, or
+        // if we already have all the distances we need, 
+        // terminate
+        if (sd.reached_max ||
+            (targets != null && to_get.isEmpty()) ||
+            (sd.distances.size() >= numDests))
+        {
+            return sd.distances;
+        }
+        
+        while (!sd.unknownVertices.isEmpty() && (sd.distances.size() < numDests || !to_get.isEmpty()))
+        {
+            Map.Entry<V,Number> p = sd.getNextVertex();
+            V v = p.getKey();
+            double v_dist = p.getValue().doubleValue();
+            to_get.remove(v);
+            if (v_dist > this.max_distance) 
+            {
+                // we're done; put this vertex back in so that we're not including
+                // a distance beyond what we specified
+                sd.restoreVertex(v, v_dist);
+                sd.reached_max = true;
+                break;
+            }
+            sd.dist_reached = v_dist;
+
+            if (sd.distances.size() >= this.max_targets)
+            {
+                sd.reached_max = true;
+                break;
+            }
+            
+            for (E e : getEdgesToCheck(v) )
+            {
+                for (V w : g.getIncidentVertices(e))
+                {
+                    if (!sd.distances.containsKey(w))
+                    {
+                        double edge_weight = nev.apply(e).doubleValue();
+                        if (edge_weight < 0)
+                            throw new IllegalArgumentException("Edges weights must be non-negative");
+                        double new_dist = v_dist + edge_weight;
+                        if (!sd.estimatedDistances.containsKey(w))
+                        {
+                            sd.createRecord(w, e, new_dist);
+                        }
+                        else
+                        {
+                            double w_dist = ((Double)sd.estimatedDistances.get(w)).doubleValue();
+                            if (new_dist < w_dist) // update tentative distance & path for w
+                                sd.update(w, e, new_dist);
+                        }
+                    }
+                }
+            }
+        }
+        return sd.distances;
+    }
+
+    protected SourceData getSourceData(V source)
+    {
+        SourceData sd = sourceMap.get(source);
+        if (sd == null)
+            sd = new SourceData(source);
+        return sd;
+    }
+    
+    /**
+     * Returns the set of edges incident to <code>v</code> that should be tested.
+     * By default, this is the set of outgoing edges for instances of <code>Graph</code>,
+     * the set of incident edges for instances of <code>Hypergraph</code>,
+     * and is otherwise undefined.
+     * @param v the vertex whose edges are to be checked
+     * @return the set of edges incident to {@code v} that should be tested
+     */
+    protected Collection<E> getEdgesToCheck(V v)
+    {
+        if (g instanceof Graph)
+            return ((Graph<V,E>)g).getOutEdges(v);
+        else
+            return g.getIncidentEdges(v);
+
+    }
+
+    
+    /**
+     * Returns the length of a shortest path from the source to the target vertex,
+     * or null if the target is not reachable from the source.
+     * If either vertex is not in the graph for which this instance
+     * was created, throws <code>IllegalArgumentException</code>.
+     * 
+     * @param source the vertex from which the distance to {@code target} is to be measured
+     * @param target the vertex to which the distance from {@code source} is to be measured
+     * @return the distance between {@code source} and {@code target}
+     * 
+     * @see #getDistanceMap(Object)
+     * @see #getDistanceMap(Object,int)
+     */
+    public Number getDistance(V source, V target)
+    {
+        if (g.containsVertex(target) == false)
+            throw new IllegalArgumentException("Specified target vertex " + 
+                    target + " is not part of graph " + g);
+        if (g.containsVertex(source) == false)
+            throw new IllegalArgumentException("Specified source vertex " + 
+                    source + " is not part of graph " + g);
+        
+        Set<V> targets = new HashSet<V>();
+        targets.add(target);
+        Map<V,Number> distanceMap = getDistanceMap(source, targets);
+        return distanceMap.get(target);
+    }
+    
+
+    /**
+     * Returns a {@code Map} from each element {@code t} of {@code targets} to the 
+     * shortest-path distance from {@code source} to {@code t}. 
+     * @param source the vertex from which the distance to each target is to be measured
+     * @param targets the vertices to which the distance from the source is to be measured
+     * @return {@code Map} from each element of {@code targets} to its distance from {@code source}
+     */
+    public Map<V,Number> getDistanceMap(V source, Collection<V> targets)
+    {
+       if (g.containsVertex(source) == false)
+            throw new IllegalArgumentException("Specified source vertex " + 
+                    source + " is not part of graph " + g);
+       if (targets.size() > max_targets)
+            throw new IllegalArgumentException("size of target set exceeds maximum " +
+                    "number of targets allowed: " + this.max_targets);
+        
+        Map<V,Number> distanceMap = 
+        	singleSourceShortestPath(source, targets, 
+        			Math.min(g.getVertexCount(), max_targets));
+        if (!cached)
+            reset(source);
+        
+        return distanceMap;
+    }
+    
+    /**
+     * <p>Returns a <code>LinkedHashMap</code> which maps each vertex 
+     * in the graph (including the <code>source</code> vertex) 
+     * to its distance from the <code>source</code> vertex.
+     * The map's iterator will return the elements in order of 
+     * increasing distance from <code>source</code>.
+     * 
+     * <p>The size of the map returned will be the number of 
+     * vertices reachable from <code>source</code>.
+     * 
+     * @see #getDistanceMap(Object,int)
+     * @see #getDistance(Object,Object)
+     * @param source    the vertex from which distances are measured
+     * @return a mapping from each vertex in the graph to its distance from {@code source}
+     */
+    public Map<V,Number> getDistanceMap(V source)
+    {
+        return getDistanceMap(source, Math.min(g.getVertexCount(), max_targets));
+    }
+    
+
+
+    /**
+     * <p>Returns a <code>LinkedHashMap</code> which maps each of the closest 
+     * <code>numDist</code> vertices to the <code>source</code> vertex 
+     * in the graph (including the <code>source</code> vertex) 
+     * to its distance from the <code>source</code> vertex.  Throws 
+     * an <code>IllegalArgumentException</code> if <code>source</code>
+     * is not in this instance's graph, or if <code>numDests</code> is 
+     * either less than 1 or greater than the number of vertices in the 
+     * graph.
+     * 
+     * <p>The size of the map returned will be the smaller of 
+     * <code>numDests</code> and the number of vertices reachable from
+     * <code>source</code>. 
+     * 
+     * @see #getDistanceMap(Object)
+     * @see #getDistance(Object,Object)
+     * @param source    the vertex from which distances are measured
+     * @param numDests  the number of vertices for which to measure distances
+     * @return a mapping from the {@code numDests} vertices in the graph
+     *     closest to {@code source}, to their distance from {@code source}
+     *   
+     */
+    public LinkedHashMap<V,Number> getDistanceMap(V source, int numDests)
+    {
+
+    	if(g.getVertices().contains(source) == false) {
+            throw new IllegalArgumentException("Specified source vertex " + 
+                    source + " is not part of graph " + g);
+    		
+    	}
+        if (numDests < 1 || numDests > g.getVertexCount())
+            throw new IllegalArgumentException("numDests must be >= 1 " + 
+                "and <= g.numVertices()");
+
+        if (numDests > max_targets)
+            throw new IllegalArgumentException("numDests must be <= the maximum " +
+                    "number of targets allowed: " + this.max_targets);
+            
+        LinkedHashMap<V,Number> distanceMap = 
+        	singleSourceShortestPath(source, null, numDests);
+                
+        if (!cached)
+            reset(source);
+        
+        return distanceMap;        
+    }
+    
+    /**
+     * Allows the user to specify the maximum distance that this instance will calculate.
+     * Any vertices past this distance will effectively be unreachable from the source, in
+     * the sense that the algorithm will not calculate the distance to any vertices which
+     * are farther away than this distance.  A negative value for <code>max_dist</code> 
+     * will ensure that no further distances are calculated.
+     * 
+     * <p>This can be useful for limiting the amount of time and space used by this algorithm
+     * if the graph is very large.
+     * 
+     * <p>Note: if this instance has already calculated distances greater than <code>max_dist</code>,
+     * and the results are cached, those results will still be valid and available; this limit
+     * applies only to subsequent distance calculations.
+     * 
+     * @param max_dist the maximum distance that this instance will calculate
+     * 
+     * @see #setMaxTargets(int)
+     */
+    public void setMaxDistance(double max_dist)
+    {
+        this.max_distance = max_dist;
+        for (V v : sourceMap.keySet())
+        {
+            SourceData sd = sourceMap.get(v);
+            sd.reached_max = (this.max_distance <= sd.dist_reached) || (sd.distances.size() >= max_targets);
+        }
+    }
+       
+    /**
+     * Allows the user to specify the maximum number of target vertices per source vertex 
+     * for which this instance will calculate distances.  Once this threshold is reached, 
+     * any further vertices will effectively be unreachable from the source, in
+     * the sense that the algorithm will not calculate the distance to any more vertices.  
+     * A negative value for <code>max_targets</code> will ensure that no further distances are calculated.
+     * 
+     * <p>This can be useful for limiting the amount of time and space used by this algorithm
+     * if the graph is very large.
+     * 
+     * <p>Note: if this instance has already calculated distances to a greater number of 
+     * targets than <code>max_targets</code>, and the results are cached, those results 
+     * will still be valid and available; this limit applies only to subsequent distance 
+     * calculations.
+     * 
+     * @param max_targets the maximum number of targets for which this instance will calculate
+     *     distances
+     * 
+     * @see #setMaxDistance(double)
+     */
+    public void setMaxTargets(int max_targets)
+    {
+        this.max_targets = max_targets;
+        for (V v : sourceMap.keySet())
+        {
+            SourceData sd = sourceMap.get(v);
+            sd.reached_max = (this.max_distance <= sd.dist_reached) || (sd.distances.size() >= max_targets);
+        }
+    }
+    
+    /**
+     * Clears all stored distances for this instance.  
+     * Should be called whenever the graph is modified (edge weights 
+     * changed or edges added/removed).  If the user knows that
+     * some currently calculated distances are unaffected by a
+     * change, <code>reset(V)</code> may be appropriate instead.
+     * 
+     * @see #reset(Object)
+     */
+    public void reset()
+    {
+        sourceMap = new HashMap<V,SourceData>();
+    }
+        
+    /**
+     * Specifies whether or not this instance of <code>DijkstraShortestPath</code>
+     * should cache its results (final and partial) for future reference.
+     * 
+     * @param enable    <code>true</code> if the results are to be cached, and
+     *                  <code>false</code> otherwise
+     */
+    public void enableCaching(boolean enable)
+    {
+        this.cached = enable;
+    }
+    
+    /**
+     * Clears all stored distances for the specified source vertex 
+     * <code>source</code>.  Should be called whenever the stored distances
+     * from this vertex are invalidated by changes to the graph.
+     * 
+     * @param source the vertex for which stored distances should be cleared
+     * 
+     * @see #reset()
+     */
+    public void reset(V source)
+    {
+        sourceMap.put(source, null);
+    }
+
+    /**
+     * Compares according to distances, so that the BinaryHeap knows how to 
+     * order the tree.  
+     */
+    protected static class VertexComparator<V> implements Comparator<V>
+    {
+        private Map<V,Number> distances;
+        
+        protected VertexComparator(Map<V,Number> distances)
+        {
+            this.distances = distances;
+        }
+
+        public int compare(V o1, V o2)
+        {
+            return ((Double) distances.get(o1)).compareTo((Double) distances.get(o2));
+        }
+    }
+    
+    /**
+     * For a given source vertex, holds the estimated and final distances, 
+     * tentative and final assignments of incoming edges on the shortest path from
+     * the source vertex, and a priority queue (ordered by estimated distance)
+     * of the vertices for which distances are unknown.
+     * 
+     * @author Joshua O'Madadhain
+     */
+    protected class SourceData
+    {
+        protected LinkedHashMap<V,Number> distances;
+        protected Map<V,Number> estimatedDistances;
+        protected MapBinaryHeap<V> unknownVertices;
+        protected boolean reached_max = false;
+        protected double dist_reached = 0;
+
+        protected SourceData(V source)
+        {
+            distances = new LinkedHashMap<V,Number>();
+            estimatedDistances = new HashMap<V,Number>();
+            unknownVertices = new MapBinaryHeap<V>(new VertexComparator<V>(estimatedDistances));
+            
+            sourceMap.put(source, this);
+            
+            // initialize priority queue
+            estimatedDistances.put(source, new Double(0)); // distance from source to itself is 0
+            unknownVertices.add(source);
+            reached_max = false;
+            dist_reached = 0;
+        }
+        
+        protected Map.Entry<V,Number> getNextVertex()
+        {
+            V v = unknownVertices.remove();
+            Double dist = (Double)estimatedDistances.remove(v);
+            distances.put(v, dist);
+            return new BasicMapEntry<V,Number>(v, dist);
+        }
+        
+        protected void update(V dest, E tentative_edge, double new_dist)
+        {
+            estimatedDistances.put(dest, new_dist);
+            unknownVertices.update(dest);
+        }
+        
+        protected void createRecord(V w, E e, double new_dist)
+        {
+            estimatedDistances.put(w, new_dist);
+            unknownVertices.add(w);
+        }
+        
+        protected void restoreVertex(V v, double dist) 
+        {
+            estimatedDistances.put(v, dist);
+            unknownVertices.add(v);
+            distances.remove(v);
+        }
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/DijkstraShortestPath.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/DijkstraShortestPath.java
new file mode 100644
index 0000000..a3c61da
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/DijkstraShortestPath.java
@@ -0,0 +1,297 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * <p>Calculates distances and shortest paths using Dijkstra's   
+ * single-source-shortest-path algorithm.  This is a lightweight
+ * extension of <code>DijkstraDistance</code> that also stores
+ * path information, so that the shortest paths can be reconstructed.
+ * 
+ * <p> The elements in the maps returned by 
+ * <code>getIncomingEdgeMap</code> are ordered (that is, returned 
+ * by the iterator) by nondecreasing distance from <code>source</code>.
+ * 
+ * @author Joshua O'Madadhain
+ * @author Tom Nelson converted to jung2
+ * @see DijkstraDistance
+ */
+public class DijkstraShortestPath<V,E> extends DijkstraDistance<V,E> implements ShortestPath<V,E>
+{
+    /**
+     * <p>Creates an instance of <code>DijkstraShortestPath</code> for 
+     * the specified graph and the specified method of extracting weights 
+     * from edges, which caches results locally if and only if 
+     * <code>cached</code> is <code>true</code>.
+     * 
+     * @param g     the graph on which distances will be calculated
+     * @param nev   the class responsible for returning weights for edges
+     * @param cached    specifies whether the results are to be cached
+     */
+    public DijkstraShortestPath(Graph<V,E> g, Function<E, ? extends Number> nev, boolean cached)
+    {
+        super(g, nev, cached);
+    }
+    
+    /**
+     * <p>Creates an instance of <code>DijkstraShortestPath</code> for 
+     * the specified graph and the specified method of extracting weights 
+     * from edges, which caches results locally.
+     * 
+     * @param g     the graph on which distances will be calculated
+     * @param nev   the class responsible for returning weights for edges
+     */
+    public DijkstraShortestPath(Graph<V,E> g, Function<E, ? extends Number> nev)
+    {
+        super(g, nev);
+    }
+    
+    /**
+     * <p>Creates an instance of <code>DijkstraShortestPath</code> for 
+     * the specified unweighted graph (that is, all weights 1) which
+     * caches results locally.
+     * 
+     * @param g     the graph on which distances will be calculated
+     */ 
+    public DijkstraShortestPath(Graph<V,E> g)
+    {
+        super(g);
+    }
+
+    /**
+     * <p>Creates an instance of <code>DijkstraShortestPath</code> for 
+     * the specified unweighted graph (that is, all weights 1) which
+     * caches results locally.
+     * 
+     * @param g     the graph on which distances will be calculated
+     * @param cached    specifies whether the results are to be cached
+     */ 
+    public DijkstraShortestPath(Graph<V,E> g, boolean cached)
+    {
+        super(g, cached);
+    }
+    
+    @Override
+    protected SourceData getSourceData(V source)
+    {
+        SourceData sd = sourceMap.get(source);
+        if (sd == null)
+            sd = new SourcePathData(source);
+        return sd;
+    }
+    
+    /**
+     * <p>Returns the last edge on a shortest path from <code>source</code>
+     * to <code>target</code>, or null if <code>target</code> is not 
+     * reachable from <code>source</code>.
+     * 
+     * <p>If either vertex is not in the graph for which this instance
+     * was created, throws <code>IllegalArgumentException</code>.
+     * 
+     * @param source the vertex where the shortest path starts
+     * @param target the vertex where the shortest path ends
+     * @return the last edge on a shortest path from {@code source} to {@code target}
+     *     or null if {@code target} is not reachable from {@code source}
+     */
+	public E getIncomingEdge(V source, V target)
+	{
+        if (!g.containsVertex(source))
+            throw new IllegalArgumentException("Specified source vertex " + 
+                    source + " is not part of graph " + g);
+        
+        if (!g.containsVertex(target))
+            throw new IllegalArgumentException("Specified target vertex " + 
+                    target + " is not part of graph " + g);
+
+        Set<V> targets = new HashSet<V>();
+        targets.add(target);
+        singleSourceShortestPath(source, targets, g.getVertexCount());
+        @SuppressWarnings("unchecked")
+		Map<V,E> incomingEdgeMap = 
+            ((SourcePathData)sourceMap.get(source)).incomingEdges;
+        E incomingEdge = incomingEdgeMap.get(target);
+        
+        if (!cached)
+            reset(source);
+        
+        return incomingEdge;
+	}
+
+    /**
+     * <p>Returns a <code>LinkedHashMap</code> which maps each vertex 
+     * in the graph (including the <code>source</code> vertex) 
+     * to the last edge on the shortest path from the 
+     * <code>source</code> vertex.
+     * The map's iterator will return the elements in order of 
+     * increasing distance from <code>source</code>.
+     * 
+     * @see DijkstraDistance#getDistanceMap(Object,int)
+     * @see DijkstraDistance#getDistance(Object,Object)
+     * @param source    the vertex from which distances are measured
+     */
+    public Map<V,E> getIncomingEdgeMap(V source)
+	{
+		return getIncomingEdgeMap(source, g.getVertexCount());
+	}
+
+    /**
+     * Returns a <code>List</code> of the edges on the shortest path from 
+     * <code>source</code> to <code>target</code>, in order of their
+     * occurrence on this path.  
+     * If either vertex is not in the graph for which this instance
+     * was created, throws <code>IllegalArgumentException</code>.
+     * 
+     * @param source the starting vertex for the path to generate
+     * @param target the ending vertex for the path to generate
+     * @return the edges on the shortest path from {@code source} to {@code target},
+     *     in order of their occurrence
+     */
+	public List<E> getPath(V source, V target)
+	{
+		if(!g.containsVertex(source)) 
+            throw new IllegalArgumentException("Specified source vertex " + 
+                    source + " is not part of graph " + g);
+        
+		if(!g.containsVertex(target)) 
+            throw new IllegalArgumentException("Specified target vertex " + 
+                    target + " is not part of graph " + g);
+        
+        LinkedList<E> path = new LinkedList<E>();
+
+        // collect path data; must use internal method rather than
+        // calling getIncomingEdge() because getIncomingEdge() may
+        // wipe out results if results are not cached
+        Set<V> targets = new HashSet<V>();
+        targets.add(target);
+        singleSourceShortestPath(source, targets, g.getVertexCount());
+        @SuppressWarnings("unchecked")
+		Map<V,E> incomingEdges = 
+            ((SourcePathData)sourceMap.get(source)).incomingEdges;
+        
+        if (incomingEdges.isEmpty() || incomingEdges.get(target) == null)
+            return path;
+        V current = target;
+        while (!current.equals(source))
+        {
+            E incoming = incomingEdges.get(current);
+            path.addFirst(incoming);
+            current = ((Graph<V,E>)g).getOpposite(current, incoming);
+        }
+		return path;
+	}
+
+    
+    /**
+     * <p>Returns a <code>LinkedHashMap</code> which maps each of the closest 
+     * <code>numDests</code> vertices to the <code>source</code> vertex 
+     * in the graph (including the <code>source</code> vertex) 
+     * to the incoming edge along the path from that vertex.  Throws 
+     * an <code>IllegalArgumentException</code> if <code>source</code>
+     * is not in this instance's graph, or if <code>numDests</code> is 
+     * either less than 1 or greater than the number of vertices in the 
+     * graph.
+     * 
+     * @see #getIncomingEdgeMap(Object)
+     * @see #getPath(Object,Object)
+     * @param source    the vertex from which distances are measured
+     * @param numDests  the number of vertices for which to measure distances
+     * @return a map from each of the closest {@code numDests} vertices
+     *     to the last edge on the shortest path to that vertex starting from {@code source}
+     */
+	public LinkedHashMap<V,E> getIncomingEdgeMap(V source, int numDests)
+	{
+        if (g.getVertices().contains(source) == false)
+            throw new IllegalArgumentException("Specified source vertex " + 
+                    source + " is not part of graph " + g);
+
+        if (numDests < 1 || numDests > g.getVertexCount())
+            throw new IllegalArgumentException("numDests must be >= 1 " + 
+            "and <= g.numVertices()");
+
+        singleSourceShortestPath(source, null, numDests);
+        
+        @SuppressWarnings("unchecked")
+		LinkedHashMap<V,E> incomingEdgeMap = 
+            ((SourcePathData)sourceMap.get(source)).incomingEdges;
+        
+        if (!cached)
+            reset(source);
+        
+        return incomingEdgeMap;        
+	}
+     
+    
+    /**
+     * For a given source vertex, holds the estimated and final distances, 
+     * tentative and final assignments of incoming edges on the shortest path from
+     * the source vertex, and a priority queue (ordered by estimaed distance)
+     * of the vertices for which distances are unknown.
+     * 
+     * @author Joshua O'Madadhain
+     */
+    protected class SourcePathData extends SourceData
+    {
+        protected Map<V,E> tentativeIncomingEdges;
+		protected LinkedHashMap<V,E> incomingEdges;
+
+		protected SourcePathData(V source)
+		{
+            super(source);
+            incomingEdges = new LinkedHashMap<V,E>();
+            tentativeIncomingEdges = new HashMap<V,E>();
+		}
+        
+        @Override
+        public void update(V dest, E tentative_edge, double new_dist)
+        {
+            super.update(dest, tentative_edge, new_dist);
+            tentativeIncomingEdges.put(dest, tentative_edge);
+        }
+        
+        @Override
+        public Map.Entry<V,Number> getNextVertex()
+        {
+            Map.Entry<V,Number> p = super.getNextVertex();
+            V v = p.getKey();
+            E incoming = tentativeIncomingEdges.remove(v);
+            incomingEdges.put(v, incoming);
+            return p;
+        }
+        
+        @Override
+        public void restoreVertex(V v, double dist)
+        {
+            super.restoreVertex(v, dist);
+            E incoming = incomingEdges.get(v);
+            tentativeIncomingEdges.put(v, incoming);
+        }
+        
+        @Override
+        public void createRecord(V w, E e, double new_dist)
+        {
+            super.createRecord(w, e, new_dist);
+            tentativeIncomingEdges.put(w, e);
+        }
+       
+    }
+
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/Distance.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/Distance.java
new file mode 100644
index 0000000..15e8e83
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/Distance.java
@@ -0,0 +1,46 @@
+/*
+ * Created on Apr 2, 2004
+ *
+ * Copyright (c) 2004, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+import java.util.Map;
+
+
+/**
+ * An interface for classes which calculate the distance between
+ * one vertex and another.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public interface Distance<V>
+{
+	/**
+	 * Returns the distance from the <code>source</code> vertex to the
+	 * <code>target</code> vertex. If <code>target</code> is not reachable from
+	 * <code>source</code>, returns null.
+	 * 
+	 * @param source the vertex from which distance is to be measured
+	 * @param target the vertex to which distance is to be measured
+	 * @return the distance from {@code source} to {@code target}
+	 */
+	Number getDistance(V source, V target);
+
+	/**
+	 * Returns a <code>Map</code> which maps each vertex in the graph (including
+	 * the <code>source</code> vertex) to its distance (represented as a Number)
+	 * from <code>source</code>. If any vertex is not reachable from
+	 * <code>source</code>, no distance is stored for that vertex.
+	 * 
+	 * @param source the vertex from which distances are to be measured
+	 * @return a {@code Map} of the distances from {@code source} to other vertices in the graph
+	 */
+	Map<V, Number> getDistanceMap(V source);
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/DistanceStatistics.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/DistanceStatistics.java
new file mode 100644
index 0000000..e4e9624
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/DistanceStatistics.java
@@ -0,0 +1,165 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.shortestpath;
+import java.util.Collection;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.scoring.ClosenessCentrality;
+import edu.uci.ics.jung.algorithms.scoring.util.VertexScoreTransformer;
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * Statistics relating to vertex-vertex distances in a graph.
+ * 
+ * <p>Formerly known as <code>GraphStatistics</code> in JUNG 1.x.
+ * 
+ * @author Scott White
+ * @author Joshua O'Madadhain
+ */
+public class DistanceStatistics 
+{
+	/**
+     * For each vertex <code>v</code> in <code>graph</code>, 
+     * calculates the average shortest path length from <code>v</code> 
+     * to all other vertices in <code>graph</code> using the metric 
+     * specified by <code>d</code>, and returns the results in a
+     * <code>Map</code> from vertices to <code>Double</code> values.
+     * If there exists an ordered pair <code><u,v></code>
+     * for which <code>d.getDistance(u,v)</code> returns <code>null</code>,
+     * then the average distance value for <code>u</code> will be stored
+     * as <code>Double.POSITIVE_INFINITY</code>).
+     * 
+     * <p>Does not include self-distances (path lengths from <code>v</code>
+     * to <code>v</code>).
+     * 
+     * <p>To calculate the average distances, ignoring edge weights if any:
+     * <pre>
+     * Map distances = DistanceStatistics.averageDistances(g, new UnweightedShortestPath(g));
+     * </pre>
+     * To calculate the average distances respecting edge weights:
+     * <pre>
+     * DijkstraShortestPath dsp = new DijkstraShortestPath(g, nev);
+     * Map distances = DistanceStatistics.averageDistances(g, dsp);
+     * </pre>
+     * where <code>nev</code> is an instance of <code>Transformer</code> that
+     * is used to fetch the weight for each edge.
+     * 
+     * @see edu.uci.ics.jung.algorithms.shortestpath.UnweightedShortestPath
+     * @see edu.uci.ics.jung.algorithms.shortestpath.DijkstraDistance
+	 * 
+	 * @param graph the graph for which distances are to be calculated
+	 * @param d the distance metric to use for the calculation
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+	 * @return a map from each vertex to the mean distance to each other (reachable) vertex
+	 */
+    public static <V,E> Function<V,Double> averageDistances(Hypergraph<V,E> graph, Distance<V> d)
+    {
+    	final ClosenessCentrality<V,E> cc = new ClosenessCentrality<V,E>(graph, d);
+    	return new VertexScoreTransformer<V, Double>(cc);
+    }
+    
+    /**
+     * For each vertex <code>v</code> in <code>g</code>, 
+     * calculates the average shortest path length from <code>v</code> 
+     * to all other vertices in <code>g</code>, ignoring edge weights.
+     * @see #diameter(Hypergraph)
+     * @see edu.uci.ics.jung.algorithms.scoring.ClosenessCentrality
+     *
+     * @param g the graph for which distances are to be calculated
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+	 * @return a map from each vertex to the mean distance to each other (reachable) vertex
+     */
+    public static <V,E> Function<V, Double> averageDistances(Hypergraph<V,E> g)
+    {
+    	final ClosenessCentrality<V,E> cc = new ClosenessCentrality<V,E>(g, 
+    			new UnweightedShortestPath<V,E>(g));
+        return new VertexScoreTransformer<V, Double>(cc);
+    }
+    
+    /**
+     * Returns the diameter of <code>g</code> using the metric 
+     * specified by <code>d</code>.  The diameter is defined to be
+     * the maximum, over all pairs of vertices <code>u,v</code>,
+     * of the length of the shortest path from <code>u</code> to 
+     * <code>v</code>.  If the graph is disconnected (that is, not 
+     * all pairs of vertices are reachable from one another), the
+     * value returned will depend on <code>use_max</code>:  
+     * if <code>use_max == true</code>, the value returned
+     * will be the the maximum shortest path length over all pairs of <b>connected</b> 
+     * vertices; otherwise it will be <code>Double.POSITIVE_INFINITY</code>.
+     * 
+	 * @param g the graph for which distances are to be calculated
+	 * @param d the distance metric to use for the calculation
+	 * @param use_max if {@code true}, return the maximum shortest path length for all graphs;
+	 *     otherwise, return {@code Double.POSITIVE_INFINITY} for disconnected graphs
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+	 * @return the longest distance from any vertex to any other
+     */
+    public static <V, E> double diameter(Hypergraph<V,E> g, Distance<V> d, boolean use_max)
+    {
+        double diameter = 0;
+        Collection<V> vertices = g.getVertices();
+        for(V v : vertices) {
+            for(V w : vertices) {
+
+                if (v.equals(w) == false) // don't include self-distances
+                {
+                    Number dist = d.getDistance(v, w);
+                    if (dist == null)
+                    {
+                        if (!use_max)
+                            return Double.POSITIVE_INFINITY;
+                    }
+                    else
+                        diameter = Math.max(diameter, dist.doubleValue());
+                }
+            }
+        }
+        return diameter;
+    }
+    
+    /**
+     * Returns the diameter of <code>g</code> using the metric 
+     * specified by <code>d</code>.  The diameter is defined to be
+     * the maximum, over all pairs of vertices <code>u,v</code>,
+     * of the length of the shortest path from <code>u</code> to 
+     * <code>v</code>, or <code>Double.POSITIVE_INFINITY</code>
+     * if any of these distances do not exist.
+     * @see #diameter(Hypergraph, Distance, boolean)
+     * 
+	 * @param g the graph for which distances are to be calculated
+	 * @param d the distance metric to use for the calculation
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+	 * @return the longest distance from any vertex to any other
+     */
+    public static <V, E> double diameter(Hypergraph<V,E> g, Distance<V> d)
+    {
+        return diameter(g, d, false);
+    }
+    
+    /**
+     * Returns the diameter of <code>g</code>, ignoring edge weights.
+     * @see #diameter(Hypergraph, Distance, boolean)
+     * 
+	 * @param g the graph for which distances are to be calculated
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+	 * @return the longest distance from any vertex to any other
+     */
+    public static <V, E> double diameter(Hypergraph<V,E> g)
+    {
+        return diameter(g, new UnweightedShortestPath<V,E>(g));
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/MinimumSpanningForest.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/MinimumSpanningForest.java
new file mode 100644
index 0000000..f6e30fb
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/MinimumSpanningForest.java
@@ -0,0 +1,163 @@
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.Forest;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * For the input Graph, creates a MinimumSpanningTree
+ * using a variation of Prim's algorithm.
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+public class MinimumSpanningForest<V,E> {
+	
+	protected Graph<V,E> graph;
+	protected Forest<V,E> forest;
+	protected Function<E, Double> weights;
+	
+	/**
+	 * Creates a Forest from the supplied Graph and supplied Supplier, which
+	 * is used to create a new, empty Forest. If non-null, the supplied root
+	 * will be used as the root of the tree/forest. If the supplied root is
+	 * null, or not present in the Graph, then an arbitrary Graph vertex
+	 * will be selected as the root.
+	 * If the Minimum Spanning Tree does not include all vertices of the
+	 * Graph, then a leftover vertex is selected as a root, and another
+	 * tree is created.
+	 * @param graph the input graph
+	 * @param Supplier the Supplier to use to create the new forest
+	 * @param root the vertex of the graph to be used as the root of the forest 
+	 * @param weights edge weights
+	 */
+	public MinimumSpanningForest(Graph<V, E> graph, Supplier<Forest<V,E>> Supplier, 
+			V root, Map<E, Double> weights) {
+		this(graph, Supplier.get(), root, weights);
+	}
+	
+	/**
+	 * Creates a minimum spanning forest from the supplied graph, populating the
+	 * supplied Forest, which must be empty. 
+	 * If the supplied root is null, or not present in the Graph,
+	 * then an arbitrary Graph vertex will be selected as the root.
+	 * If the Minimum Spanning Tree does not include all vertices of the
+	 * Graph, then a leftover vertex is selected as a root, and another
+	 * tree is created
+	 * @param graph the Graph to find MST in
+	 * @param forest the Forest to populate. Must be empty
+	 * @param root first Tree root, may be null
+	 * @param weights edge weights, may be null
+	 */
+	public MinimumSpanningForest(Graph<V, E> graph, Forest<V,E> forest, 
+			V root, Map<E, Double> weights) {
+		
+		if(forest.getVertexCount() != 0) {
+			throw new IllegalArgumentException("Supplied Forest must be empty");
+		}
+		this.graph = graph;
+		this.forest = forest;
+		if(weights != null) {
+			this.weights = Functions.forMap(weights);
+		}
+		Set<E> unfinishedEdges = new HashSet<E>(graph.getEdges());
+		if(graph.getVertices().contains(root)) {
+			this.forest.addVertex(root);
+		}
+		updateForest(forest.getVertices(), unfinishedEdges);
+	}
+	
+    /**
+     * Creates a minimum spanning forest from the supplied graph, populating the
+     * supplied Forest, which must be empty. 
+     * If the supplied root is null, or not present in the Graph,
+     * then an arbitrary Graph vertex will be selected as the root.
+     * If the Minimum Spanning Tree does not include all vertices of the
+     * Graph, then a leftover vertex is selected as a root, and another
+     * tree is created
+     * @param graph the Graph to find MST in
+     * @param forest the Forest to populate. Must be empty
+     * @param root first Tree root, may be null
+     */
+    @SuppressWarnings("unchecked")
+	public MinimumSpanningForest(Graph<V, E> graph, Forest<V,E> forest, 
+            V root) {
+        
+        if(forest.getVertexCount() != 0) {
+            throw new IllegalArgumentException("Supplied Forest must be empty");
+        }
+        this.graph = graph;
+        this.forest = forest;
+        this.weights = (Function<E, Double>) Functions.constant(1.0);
+        Set<E> unfinishedEdges = new HashSet<E>(graph.getEdges());
+        if(graph.getVertices().contains(root)) {
+            this.forest.addVertex(root);
+        }
+        updateForest(forest.getVertices(), unfinishedEdges);
+    }
+	
+	/**
+	 * @return the generated forest
+	 */
+	public Forest<V,E> getForest() {
+		return forest;
+	}
+	
+	protected void updateForest(Collection<V> tv, Collection<E> unfinishedEdges) {
+		double minCost = Double.MAX_VALUE;
+		E nextEdge = null;
+		V nextVertex = null;
+		V currentVertex = null;
+		for(E e : unfinishedEdges) {
+			
+			if(forest.getEdges().contains(e)) continue;
+			// find the lowest cost edge, get its opposite endpoint,
+			// and then update forest from its Successors
+			Pair<V> endpoints = graph.getEndpoints(e);
+			V first = endpoints.getFirst();
+			V second = endpoints.getSecond();
+			if(tv.contains(first) == true && tv.contains(second) == false) {
+				if(weights.apply(e) < minCost) {
+					minCost = weights.apply(e);
+					nextEdge = e;
+					currentVertex = first;
+					nextVertex = second;
+				}
+			}
+			if(graph.getEdgeType(e) == EdgeType.UNDIRECTED &&
+					tv.contains(second) == true && tv.contains(first) == false) {
+				if(weights.apply(e) < minCost) {
+					minCost = weights.apply(e);
+					nextEdge = e;
+					currentVertex = second;
+					nextVertex = first;
+				}
+			}
+		}
+		
+		if(nextVertex != null && nextEdge != null) {
+			unfinishedEdges.remove(nextEdge);
+			forest.addEdge(nextEdge, currentVertex, nextVertex);
+			updateForest(forest.getVertices(), unfinishedEdges);
+		}
+		Collection<V> leftovers = new HashSet<V>(graph.getVertices());
+		leftovers.removeAll(forest.getVertices());
+		if(leftovers.size() > 0) {
+			V anotherRoot = leftovers.iterator().next();
+			forest.addVertex(anotherRoot);
+			updateForest(forest.getVertices(), unfinishedEdges);
+		}
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/MinimumSpanningForest2.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/MinimumSpanningForest2.java
new file mode 100644
index 0000000..e054902
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/MinimumSpanningForest2.java
@@ -0,0 +1,106 @@
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+import java.util.Collection;
+import java.util.Set;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.cluster.WeakComponentClusterer;
+import edu.uci.ics.jung.algorithms.filters.FilterUtils;
+import edu.uci.ics.jung.graph.Forest;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.Tree;
+import edu.uci.ics.jung.graph.util.TreeUtils;
+
+/**
+ * For the input Graph, creates a MinimumSpanningTree
+ * using a variation of Prim's algorithm.
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+ at SuppressWarnings("unchecked")
+public class MinimumSpanningForest2<V,E> {
+	
+	protected Graph<V,E> graph;
+	protected Forest<V,E> forest;
+	protected Function<? super E,Double> weights = 
+		(Function<E,Double>)Functions.<Double>constant(1.0);
+	
+	/**
+	 * Create a Forest from the supplied Graph and supplied Supplier, which
+	 * is used to create a new, empty Forest. If non-null, the supplied root
+	 * will be used as the root of the tree/forest. If the supplied root is
+	 * null, or not present in the Graph, then an arbitary Graph vertex
+	 * will be selected as the root.
+	 * If the Minimum Spanning Tree does not include all vertices of the
+	 * Graph, then a leftover vertex is selected as a root, and another
+	 * tree is created
+	 * @param graph the graph for which the minimum spanning forest will be generated
+	 * @param supplier a factory for the type of forest to build
+	 * @param treeFactory a factory for the type of tree to build
+	 * @param weights edge weights; may be null
+	 */
+	public MinimumSpanningForest2(Graph<V, E> graph, 
+			Supplier<Forest<V,E>> supplier, 
+			Supplier<? extends Graph<V,E>> treeFactory,
+			Function<? super E, Double> weights) {
+		this(graph, supplier.get(), 
+				treeFactory, 
+				weights);
+	}
+	
+	/**
+	 * Create a forest from the supplied graph, populating the
+	 * supplied Forest, which must be empty. 
+	 * If the supplied root is null, or not present in the Graph,
+	 * then an arbitary Graph vertex will be selected as the root.
+	 * If the Minimum Spanning Tree does not include all vertices of the
+	 * Graph, then a leftover vertex is selected as a root, and another
+	 * tree is created
+	 * @param graph the Graph to find MST in
+	 * @param forest the Forest to populate. Must be empty
+	 * @param treeFactory a factory for the type of tree to build
+	 * @param weights edge weights, may be null
+	 */
+	public MinimumSpanningForest2(Graph<V, E> graph, 
+			Forest<V,E> forest, 
+			Supplier<? extends Graph<V,E>> treeFactory,
+			Function<? super E, Double> weights) {
+		
+		if(forest.getVertexCount() != 0) {
+			throw new IllegalArgumentException("Supplied Forest must be empty");
+		}
+		this.graph = graph;
+		this.forest = forest;
+		if(weights != null) {
+			this.weights = weights;
+		}
+		
+		WeakComponentClusterer<V,E> wcc =
+			new WeakComponentClusterer<V,E>();
+		Set<Set<V>> component_vertices = wcc.apply(graph);
+		Collection<Graph<V,E>> components = 
+			FilterUtils.createAllInducedSubgraphs(component_vertices, graph);
+		
+		for(Graph<V,E> component : components) {
+			PrimMinimumSpanningTree<V,E> mst = 
+				new PrimMinimumSpanningTree<V,E>(treeFactory, this.weights);
+			Graph<V,E> subTree = mst.apply(component);
+			if(subTree instanceof Tree) {
+				TreeUtils.addSubTree(forest, (Tree<V,E>)subTree, null, null);
+			}
+		}
+	}
+	
+	/**
+	 * @return the generated forest
+	 */
+	public Forest<V,E> getForest() {
+		return forest;
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/PrimMinimumSpanningTree.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/PrimMinimumSpanningTree.java
new file mode 100644
index 0000000..9395024
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/PrimMinimumSpanningTree.java
@@ -0,0 +1,118 @@
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * For the input Graph, creates a MinimumSpanningTree
+ * using a variation of Prim's algorithm.
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+public class PrimMinimumSpanningTree<V,E> implements Function<Graph<V,E>,Graph<V,E>> {
+	
+	protected Supplier<? extends Graph<V,E>> treeFactory;
+	protected Function<? super E,Double> weights; 
+	
+	/**
+	 * Creates an instance which generates a minimum spanning tree assuming constant edge weights.
+	 * @param supplier used to create the tree instances
+	 */
+	public PrimMinimumSpanningTree(Supplier<? extends Graph<V,E>> supplier) {
+		this(supplier, Functions.constant(1.0));
+	}
+
+    /**
+     * Creates an instance which generates a minimum spanning tree using the input edge weights.
+	 * @param supplier used to create the tree instances
+	 * @param weights the edge weights to use for defining the MST
+     */
+	public PrimMinimumSpanningTree(Supplier<? extends Graph<V,E>> supplier, 
+			Function<? super E, Double> weights) {
+		this.treeFactory = supplier;
+		if(weights != null) {
+			this.weights = weights;
+		}
+	}
+	
+	/**
+	 * @param graph the Graph to find MST in
+	 */
+    public Graph<V,E> apply(Graph<V,E> graph) {
+		Set<E> unfinishedEdges = new HashSet<E>(graph.getEdges());
+		Graph<V,E> tree = treeFactory.get();
+		V root = findRoot(graph);
+		if(graph.getVertices().contains(root)) {
+			tree.addVertex(root);
+		} else if(graph.getVertexCount() > 0) {
+			// pick an arbitrary vertex to make root
+			tree.addVertex(graph.getVertices().iterator().next());
+		}
+		updateTree(tree, graph, unfinishedEdges);
+		
+		return tree;
+	}
+    
+    protected V findRoot(Graph<V,E> graph) {
+    	for(V v : graph.getVertices()) {
+    		if(graph.getInEdges(v).size() == 0) {
+    			return v;
+    		}
+    	}
+    	// if there is no obvious root, pick any vertex
+    	if(graph.getVertexCount() > 0) {
+    		return graph.getVertices().iterator().next();
+    	}
+    	// this graph has no vertices
+    	return null;
+    }
+	
+	protected void updateTree(Graph<V,E> tree, Graph<V,E> graph, Collection<E> unfinishedEdges) {
+		Collection<V> tv = tree.getVertices();
+		double minCost = Double.MAX_VALUE;
+		E nextEdge = null;
+		V nextVertex = null;
+		V currentVertex = null;
+		for(E e : unfinishedEdges) {
+			
+			if(tree.getEdges().contains(e)) continue;
+			// find the lowest cost edge, get its opposite endpoint,
+			// and then update forest from its Successors
+			Pair<V> endpoints = graph.getEndpoints(e);
+			V first = endpoints.getFirst();
+			V second = endpoints.getSecond();
+			if((tv.contains(first) == true && tv.contains(second) == false)) {
+				if(weights.apply(e) < minCost) {
+					minCost = weights.apply(e);
+					nextEdge = e;
+					currentVertex = first;
+					nextVertex = second;
+				}
+			} else if((tv.contains(second) == true && tv.contains(first) == false)) {
+				if(weights.apply(e) < minCost) {
+					minCost = weights.apply(e);
+					nextEdge = e;
+					currentVertex = second;
+					nextVertex = first;
+				}
+			}
+		}
+		
+		if(nextVertex != null && nextEdge != null) {
+			unfinishedEdges.remove(nextEdge);
+			tree.addEdge(nextEdge, currentVertex, nextVertex);
+			updateTree(tree, graph, unfinishedEdges);
+		}
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/ShortestPath.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/ShortestPath.java
new file mode 100644
index 0000000..104ace5
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/ShortestPath.java
@@ -0,0 +1,31 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+* 
+* Created on Feb 12, 2004
+*/
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+import java.util.Map;
+
+
+/**
+ * An interface for algorithms that calculate shortest paths.
+ */
+public interface ShortestPath<V, E>
+{
+    /**
+     * Returns a map from vertices to the last edge on the shortest path to that vertex
+     * starting from {@code source}.
+     * 
+     * @param source the starting point for the shortest paths
+     * @return a map from vertices to the last edge on the shortest path to that vertex
+     *     starting from {@code source}
+     */ 
+     Map<V,E> getIncomingEdgeMap(V source);
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/ShortestPathUtils.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/ShortestPathUtils.java
new file mode 100644
index 0000000..8764543
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/ShortestPathUtils.java
@@ -0,0 +1,62 @@
+/*
+ * Created on Jul 10, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * Utilities relating to the shortest paths in a graph.
+ */
+public class ShortestPathUtils
+{
+	/**
+     * Returns a <code>List</code> of the edges on the shortest path from 
+     * <code>source</code> to <code>target</code>, in order of their
+     * occurrence on this path.  
+	 * 
+	 * @param graph the graph for which the shortest path is defined
+	 * @param sp holder of the shortest path information
+	 * @param source the vertex from which the shortest path is measured
+	 * @param target the vertex to which the shortest path is measured
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+	 * @return the edges on the shortest path from {@code source} to {@code target},
+	 *     in the order traversed
+	 */
+    public static <V, E> List<E> getPath(Graph<V,E> graph, ShortestPath<V,E> sp, V source, V target)
+    {
+        LinkedList<E> path = new LinkedList<E>();
+        
+        Map<V,E> incomingEdges = sp.getIncomingEdgeMap(source);
+        
+        if (incomingEdges.isEmpty() || incomingEdges.get(target) == null)
+            return path;
+        V current = target;
+        while (!current.equals(source))
+        {
+            E incoming = incomingEdges.get(current);
+            path.addFirst(incoming);
+            Pair<V> endpoints = graph.getEndpoints(incoming);
+            if(endpoints.getFirst().equals(current)) {	
+            	current = endpoints.getSecond();
+            } else {
+            	current = endpoints.getFirst();
+            }
+        }
+        return path;
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/UnweightedShortestPath.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/UnweightedShortestPath.java
new file mode 100644
index 0000000..af1cd8d
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/UnweightedShortestPath.java
@@ -0,0 +1,152 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * Computes the shortest path distances for graphs whose edges are not weighted (using BFS).
+ * 
+ * @author Scott White
+ */
+public class UnweightedShortestPath<V, E> 
+    implements ShortestPath<V,E>, Distance<V>
+{
+	private Map<V,Map<V,Number>> mDistanceMap;
+	private Map<V,Map<V,E>> mIncomingEdgeMap;
+	private Hypergraph<V,E> mGraph;
+    private Map<V, Number> distances = new HashMap<V,Number>();
+
+	/**
+	 * Constructs and initializes algorithm
+	 * @param g the graph
+	 */
+	public UnweightedShortestPath(Hypergraph<V,E> g)
+	{
+		mDistanceMap = new HashMap<V,Map<V,Number>>();
+		mIncomingEdgeMap = new HashMap<V,Map<V,E>>();
+		mGraph = g;
+	}
+
+    /**
+     * @see edu.uci.ics.jung.algorithms.shortestpath.Distance#getDistance(Object, Object)
+     */
+	public Number getDistance(V source, V target)
+	{
+		Map<V, Number> sourceSPMap = getDistanceMap(source);
+		return sourceSPMap.get(target);
+	}
+
+    /**
+     * @see edu.uci.ics.jung.algorithms.shortestpath.Distance#getDistanceMap(Object)
+     */
+	public Map<V,Number> getDistanceMap(V source)
+	{
+		Map<V,Number> sourceSPMap = mDistanceMap.get(source);
+		if (sourceSPMap == null)
+		{
+			computeShortestPathsFromSource(source);
+			sourceSPMap = mDistanceMap.get(source);
+		}
+		return sourceSPMap;
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.algorithms.shortestpath.ShortestPath#getIncomingEdgeMap(Object)
+	 */
+	public Map<V,E> getIncomingEdgeMap(V source)
+	{
+		Map<V,E> sourceIEMap = mIncomingEdgeMap.get(source);
+		if (sourceIEMap == null)
+		{
+			computeShortestPathsFromSource(source);
+			sourceIEMap = mIncomingEdgeMap.get(source);
+		}
+		return sourceIEMap;
+	}
+
+
+	/**
+	 * Computes the shortest path distances from a given node to all other nodes.
+	 * @param source the source node
+	 */
+	private void computeShortestPathsFromSource(V source)
+	{
+		BFSDistanceLabeler<V,E> labeler = new BFSDistanceLabeler<V,E>();
+		labeler.labelDistances(mGraph, source);
+        distances = labeler.getDistanceDecorator();
+		Map<V,Number> currentSourceSPMap = new HashMap<V,Number>();
+		Map<V,E> currentSourceEdgeMap = new HashMap<V,E>();
+
+        for(V vertex : mGraph.getVertices()) {
+            
+			Number distanceVal = distances.get(vertex);
+            // BFSDistanceLabeler uses -1 to indicate unreachable vertices;
+            // don't bother to store unreachable vertices
+            if (distanceVal != null && distanceVal.intValue() >= 0) 
+            {
+                currentSourceSPMap.put(vertex, distanceVal);
+                int minDistance = distanceVal.intValue();
+                for(E incomingEdge : mGraph.getInEdges(vertex)) 
+                {
+                	for (V neighbor : mGraph.getIncidentVertices(incomingEdge))
+                	{
+                		if (neighbor.equals(vertex))
+                			continue;
+	
+	                    Number predDistanceVal = distances.get(neighbor);
+	
+	                    int pred_distance = predDistanceVal.intValue();
+	                    if (pred_distance < minDistance && pred_distance >= 0)
+	                    {
+	                        minDistance = predDistanceVal.intValue();
+	                        currentSourceEdgeMap.put(vertex, incomingEdge);
+	                    }
+                	}
+                }
+            }
+		}
+		mDistanceMap.put(source, currentSourceSPMap);
+		mIncomingEdgeMap.put(source, currentSourceEdgeMap);
+	}
+    
+    /**
+     * Clears all stored distances for this instance.  
+     * Should be called whenever the graph is modified (edge weights 
+     * changed or edges added/removed).  If the user knows that
+     * some currently calculated distances are unaffected by a
+     * change, <code>reset(V)</code> may be appropriate instead.
+     * 
+     * @see #reset(Object)
+     */
+    public void reset()
+    {
+        mDistanceMap.clear();
+        mIncomingEdgeMap.clear();
+    }
+    
+    /**
+     * Clears all stored distances for the specified source vertex 
+     * <code>source</code>.  Should be called whenever the stored distances
+     * from this vertex are invalidated by changes to the graph.
+     * 
+     * @see #reset()
+     * 
+     * @param v the vertex for which distances should be cleared
+     */
+    public void reset(V v)
+    {
+        mDistanceMap.remove(v);
+        mIncomingEdgeMap.remove(v);
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/package.html
new file mode 100644
index 0000000..d9d00be
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/shortestpath/package.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Provides interfaces and classes for calculating (geodesic) distances and shortest paths.  Currently includes:
+<ul>
+<li><code>DijkstraDistance</code>: finds the distances from a specified source vertex to other vertices in a 
+weighted graph with no negative cycles
+<li><code>DijkstraShortestPath</code>: extends <code>DijkstraDistance</code>, also finds shortest paths
+<li><code>Distance</code>: an interface for defining vertex-vertex distances
+<li><code>PrimMinimumSpanningTree</code>: identifies the spanning tree for a graph of least total edge weight
+<li><code>ShortestPath</code>: an interface for shortest-path algorithms
+<li><code>ShortestPathUtils</code>: utility functions for manipulating shortest paths
+<li><code>UnweightedShortestPath</code>: finds the distances from a specified source vertex to other vertices in an
+unweighted graph
+</ul>
+
+</body>
+</html>
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/DirectionTransformer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/DirectionTransformer.java
new file mode 100644
index 0000000..490558f
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/DirectionTransformer.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Apr 21, 2004
+ */
+package edu.uci.ics.jung.algorithms.transformation;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * <p>Functions for transforming graphs into directed or undirected graphs.
+ * 
+ * 
+ * @author Danyel Fisher
+ * @author Joshua O'Madadhain
+ */
+public class DirectionTransformer 
+{
+
+    /**
+     * Transforms <code>graph</code> (which may be of any directionality)
+     * into an undirected graph. (This may be useful for
+     * visualization tasks).
+     * Specifically:
+     * <ul>
+     * <li>Vertices are copied from <code>graph</code>.
+     * <li>Directed edges are 'converted' into a single new undirected edge in the new graph.
+     * <li>Each undirected edge (if any) in <code>graph</code> is 'recreated' with a new undirected edge in the new
+     * graph if <code>create_new</code> is true, or copied from <code>graph</code> otherwise.
+     * </ul>
+     * 
+     * @param graph     the graph to be transformed
+     * @param create_new specifies whether existing undirected edges are to be copied or recreated
+     * @param graph_factory used to create the new graph object
+     * @param edge_factory used to create new edges
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @return          the transformed <code>Graph</code>
+     */
+    public static <V,E> UndirectedGraph<V,E> toUndirected(Graph<V,E> graph, 
+    		Supplier<UndirectedGraph<V,E>> graph_factory,
+            Supplier<E> edge_factory, boolean create_new)
+    {
+        UndirectedGraph<V,E> out = graph_factory.get();
+        
+        for (V v : graph.getVertices())
+            out.addVertex(v);
+        
+        for (E e : graph.getEdges())
+        {
+            Pair<V> endpoints = graph.getEndpoints(e);
+            V v1 = endpoints.getFirst();
+            V v2 = endpoints.getSecond();
+            E to_add;
+            if (graph.getEdgeType(e) == EdgeType.DIRECTED || create_new)
+                to_add = edge_factory.get();
+            else
+                to_add = e;
+            out.addEdge(to_add, v1, v2, EdgeType.UNDIRECTED);
+        }
+        return out;
+    }
+    
+    /**
+     * Transforms <code>graph</code> (which may be of any directionality)
+     * into a directed graph.  
+     * Specifically:
+     * <ul>
+     * <li>Vertices are copied from <code>graph</code>.
+     * <li>Undirected edges are 'converted' into two new antiparallel directed edges in the new graph.
+     * <li>Each directed edge (if any) in <code>graph</code> is 'recreated' with a new edge in the new
+     * graph if <code>create_new</code> is true, or copied from <code>graph</code> otherwise.
+     * </ul>
+     * 
+     * @param graph     the graph to be transformed
+     * @param create_new specifies whether existing directed edges are to be copied or recreated
+     * @param graph_factory used to create the new graph object
+     * @param edge_factory used to create new edges
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @return          the transformed <code>Graph</code>
+     */
+    public static <V,E> Graph<V,E> toDirected(Graph<V,E> graph, Supplier<DirectedGraph<V,E>> graph_factory,
+            Supplier<E> edge_factory, boolean create_new)
+    {
+        DirectedGraph<V,E> out = graph_factory.get();
+        
+        for (V v : graph.getVertices())
+            out.addVertex(v);
+        
+        for (E e : graph.getEdges())
+        {
+            Pair<V> endpoints = graph.getEndpoints(e);
+            if (graph.getEdgeType(e) == EdgeType.UNDIRECTED)
+            {
+                V v1 = endpoints.getFirst();
+                V v2 = endpoints.getSecond();
+                out.addEdge(edge_factory.get(), v1, v2, EdgeType.DIRECTED);
+                out.addEdge(edge_factory.get(), v2, v1, EdgeType.DIRECTED);
+            }
+            else // if the edge is directed, just add it 
+            {
+                V source = graph.getSource(e);
+                V dest = graph.getDest(e);
+                E to_add = create_new ? edge_factory.get() : e;
+                out.addEdge(to_add, source, dest, EdgeType.DIRECTED);
+            }
+                
+        }
+        return out;
+    }
+}
\ No newline at end of file
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/FoldingTransformer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/FoldingTransformer.java
new file mode 100644
index 0000000..e0909af
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/FoldingTransformer.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Apr 21, 2004
+ */
+package edu.uci.ics.jung.algorithms.transformation;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.graph.KPartiteGraph;
+
+/**
+ * Methods for creating a "folded" graph based on a k-partite graph or a
+ * hypergraph.  
+ * 
+ * <p>A "folded" graph is derived from a k-partite graph by identifying
+ * a partition of vertices which will become the vertices of the new graph, copying
+ * these vertices into the new graph, and then connecting those vertices whose
+ * original analogues were connected indirectly through elements
+ * of other partitions.
+ * 
+ * <p>A "folded" graph is derived from a hypergraph by creating vertices based on
+ * either the vertices or the hyperedges of the original graph, and connecting 
+ * vertices in the new graph if their corresponding vertices/hyperedges share a 
+ * connection with a common hyperedge/vertex.   
+ * 
+ * @author Danyel Fisher
+ * @author Joshua O'Madadhain
+ */
+public class FoldingTransformer<V,E>
+{
+    
+    /**
+     * Converts <code>g</code> into a unipartite graph whose vertex set is the
+     * vertices of <code>g</code>'s partition <code>p</code>.  For vertices
+     * <code>a</code> and <code>b</code> in this partition, the resultant
+     * graph will include the edge <code>(a,b)</code> if the original graph
+     * contains edges <code>(a,c)</code> and <code>(c,b)</code> for at least
+     * one vertex <code>c</code>.
+     * 
+     * <p>The vertices of the new graph are the same as the vertices of the
+     * appropriate partition in the old graph; the edges in the new graph are
+     * created by the input edge <code>Factory</code>.
+     * 
+     * <p>If there is more than 1 such vertex <code>c</code> for a given pair
+     * <code>(a,b)</code>, the type of the output graph will determine whether
+     * it will contain parallel edges or not.
+     * 
+     * <p>This function will not create self-loops.
+     * 
+     * @param <V> vertex type
+     * @param <E> input edge type
+     * @param g input k-partite graph
+     * @param p predicate specifying vertex partition
+     * @param graph_factory Supplier used to create the output graph 
+     * @param edge_factory Supplier used to create the edges in the new graph
+     * @return a copy of the input graph folded with respect to the input partition
+     */
+    public static <V,E> Graph<V,E> foldKPartiteGraph(KPartiteGraph<V,E> g, Predicate<V> p, 
+            Supplier<Graph<V,E>> graph_factory, Supplier<E> edge_factory)
+    {
+        Graph<V,E> newGraph = graph_factory.get();
+
+        // get vertices for the specified partition
+        Collection<V> vertices = g.getVertices(p);
+        for (V v : vertices)
+        {
+            newGraph.addVertex(v);
+            for (V s : g.getSuccessors(v))
+            {
+                for (V t : g.getSuccessors(s))
+                {
+                    if (!vertices.contains(t) || t.equals(v)) 
+                        continue;
+                    newGraph.addVertex(t);
+                    newGraph.addEdge(edge_factory.get(), v, t);
+                }
+            }
+        }
+        return newGraph;
+    }
+
+    /**
+     * Converts <code>g</code> into a unipartite graph whose vertices are the
+     * vertices of <code>g</code>'s partition <code>p</code>, and whose edges
+     * consist of collections of the intermediate vertices from other partitions.  
+     * For vertices
+     * <code>a</code> and <code>b</code> in this partition, the resultant
+     * graph will include the edge <code>(a,b)</code> if the original graph
+     * contains edges <code>(a,c)</code> and <code>(c,b)</code> for at least
+     * one vertex <code>c</code>.
+     * 
+     * <p>The vertices of the new graph are the same as the vertices of the
+     * appropriate partition in the old graph; the edges in the new graph are
+     * collections of the intermediate vertices <code>c</code>.
+     * 
+     * <p>This function will not create self-loops.
+     * 
+     * @param <V> vertex type
+     * @param <E> input edge type
+     * @param g input k-partite graph
+     * @param p predicate specifying vertex partition
+     * @param graph_factory Supplier used to create the output graph 
+     * @return the result of folding g into unipartite graph whose vertices
+     * are those of the <code>p</code> partition of g
+     */
+    public static <V,E> Graph<V, Collection<V>> foldKPartiteGraph(KPartiteGraph<V,E> g, Predicate<V> p, 
+            Supplier<Graph<V, Collection<V>>> graph_factory)
+    {
+        Graph<V, Collection<V>> newGraph = graph_factory.get();
+
+        // get vertices for the specified partition, copy into new graph
+        Collection<V> vertices = g.getVertices(p);
+
+        for (V v : vertices)
+        {
+            newGraph.addVertex(v);
+            for (V s : g.getSuccessors(v))
+            {
+                for (V t : g.getSuccessors(s))
+                {
+                    if (!vertices.contains(t) || t.equals(v)) 
+                        continue;
+                    newGraph.addVertex(t);
+                    Collection<V> v_coll = newGraph.findEdge(v, t);
+                    if (v_coll == null)
+                    {
+                        v_coll = new ArrayList<V>();
+                        newGraph.addEdge(v_coll, v, t);
+                    }
+                    v_coll.add(s);
+                }
+            }
+        }
+        return newGraph;
+    }
+    
+    /**
+     * Creates a <code>Graph</code> which is an edge-folded version of <code>h</code>, where
+     * hyperedges are replaced by k-cliques in the output graph.
+     * 
+     * <p>The vertices of the new graph are the same objects as the vertices of 
+     * <code>h</code>, and <code>a</code> 
+     * is connected to <code>b</code> in the new graph if the corresponding vertices
+     * in <code>h</code> are connected by a hyperedge.  Thus, each hyperedge with 
+     * <i>k</i> vertices in <code>h</code> induces a <i>k</i>-clique in the new graph.
+     * 
+     * <p>The edges of the new graph consist of collections of each hyperedge that connected
+     * the corresponding vertex pair in the original graph.
+     * 
+     * @param <V> vertex type
+     * @param <E> input edge type
+     * @param h hypergraph to be folded
+     * @param graph_factory Supplier used to generate the output graph
+     * @return a copy of the input graph where hyperedges are replaced by cliques
+     */
+    public static <V,E> Graph<V, Collection<E>> foldHypergraphEdges(Hypergraph<V,E> h, 
+            Supplier<Graph<V, Collection<E>>> graph_factory)
+    {
+        Graph<V, Collection<E>> target = graph_factory.get();
+
+        for (V v : h.getVertices())
+            target.addVertex(v);
+        
+        for (E e : h.getEdges())
+        {
+            ArrayList<V> incident = new ArrayList<V>(h.getIncidentVertices(e));
+            populateTarget(target, e, incident);
+        }
+        return target;
+    }
+
+
+    /**
+     * Creates a <code>Graph</code> which is an edge-folded version of <code>h</code>, where
+     * hyperedges are replaced by k-cliques in the output graph.
+     * 
+     * <p>The vertices of the new graph are the same objects as the vertices of 
+     * <code>h</code>, and <code>a</code> 
+     * is connected to <code>b</code> in the new graph if the corresponding vertices
+     * in <code>h</code> are connected by a hyperedge.  Thus, each hyperedge with 
+     * <i>k</i> vertices in <code>h</code> induces a <i>k</i>-clique in the new graph.
+     * 
+     * <p>The edges of the new graph are generated by the specified edge Supplier.
+     * 
+     * @param <V> vertex type
+     * @param <E> input edge type
+     * @param h hypergraph to be folded
+     * @param graph_factory Supplier used to generate the output graph
+     * @param edge_factory Supplier used to create the new edges 
+     * @return a copy of the input graph where hyperedges are replaced by cliques
+     */
+    public static <V,E> Graph<V,E> foldHypergraphEdges(Hypergraph<V,E> h, 
+            Supplier<Graph<V,E>> graph_factory, Supplier<E> edge_factory)
+    {
+        Graph<V,E> target = graph_factory.get();
+
+        for (V v : h.getVertices())
+            target.addVertex(v);
+        
+        for (E e : h.getEdges())
+        {
+            ArrayList<V> incident = new ArrayList<V>(h.getIncidentVertices(e));
+            for (int i = 0; i < incident.size(); i++)
+                for (int j = i+1; j < incident.size(); j++)
+                    target.addEdge(edge_factory.get(), incident.get(i), incident.get(j));
+        }
+        return target;
+    }
+
+    /**
+     * Creates a <code>Graph</code> which is a vertex-folded version of <code>h</code>, whose
+     * vertices are the input's hyperedges and whose edges are induced by adjacent hyperedges
+     * in the input.
+     * 
+     * <p>The vertices of the new graph are the same objects as the hyperedges of 
+     * <code>h</code>, and <code>a</code> 
+     * is connected to <code>b</code> in the new graph if the corresponding edges
+     * in <code>h</code> have a vertex in common.  Thus, each vertex incident to  
+     * <i>k</i> edges in <code>h</code> induces a <i>k</i>-clique in the new graph.
+     * 
+     * <p>The edges of the new graph are created by the specified Supplier.
+     * 
+     * @param <V> vertex type
+     * @param <E> input edge type
+     * @param <F> output edge type
+     * @param h hypergraph to be folded
+     * @param graph_factory Supplier used to generate the output graph
+     * @param edge_factory Supplier used to generate the output edges
+     * @return a transformation of the input graph whose vertices correspond to the input's hyperedges 
+     * and edges are induced by hyperedges sharing vertices in the input
+     */
+    public static <V,E,F> Graph<E,F> foldHypergraphVertices(Hypergraph<V,E> h, 
+            Supplier<Graph<E,F>> graph_factory, Supplier<F> edge_factory)
+    {
+        Graph<E,F> target = graph_factory.get();
+        
+        for (E e : h.getEdges())
+            target.addVertex(e);
+        
+        for (V v : h.getVertices())
+        {
+            ArrayList<E> incident = new ArrayList<E>(h.getIncidentEdges(v));
+            for (int i = 0; i < incident.size(); i++)
+                for (int j = i+1; j < incident.size(); j++)
+                    target.addEdge(edge_factory.get(), incident.get(i), incident.get(j));
+        }
+        
+        return target;
+    }
+
+    /**
+     * Creates a <code>Graph</code> which is a vertex-folded version of <code>h</code>, whose
+     * vertices are the input's hyperedges and whose edges are induced by adjacent hyperedges
+     * in the input.
+     * 
+     * <p>The vertices of the new graph are the same objects as the hyperedges of 
+     * <code>h</code>, and <code>a</code> 
+     * is connected to <code>b</code> in the new graph if the corresponding edges
+     * in <code>h</code> have a vertex in common.  Thus, each vertex incident to  
+     * <i>k</i> edges in <code>h</code> induces a <i>k</i>-clique in the new graph.
+     * 
+     * <p>The edges of the new graph consist of collections of each vertex incident to 
+     * the corresponding hyperedge pair in the original graph.
+     * 
+     * @param h hypergraph to be folded
+     * @param graph_factory Supplier used to generate the output graph
+     * @return a transformation of the input graph whose vertices correspond to the input's hyperedges 
+     * and edges are induced by hyperedges sharing vertices in the input
+     */
+    public Graph<E,Collection<V>> foldHypergraphVertices(Hypergraph<V,E> h, 
+            Supplier<Graph<E,Collection<V>>> graph_factory)
+    {
+        Graph<E,Collection<V>> target = graph_factory.get();
+
+        for (E e : h.getEdges())
+            target.addVertex(e);
+        
+        for (V v : h.getVertices())
+        {
+            ArrayList<E> incident = new ArrayList<E>(h.getIncidentEdges(v));
+            populateTarget(target, v, incident);
+        }
+        return target;
+    }
+    
+    /**
+     * @param target
+     * @param e
+     * @param incident
+     */
+    private static <S,T> void populateTarget(Graph<S, Collection<T>> target, T e,
+            ArrayList<S> incident)
+    {
+        for (int i = 0; i < incident.size(); i++)
+        {
+            S v1 = incident.get(i);
+            for (int j = i+1; j < incident.size(); j++)
+            {
+                S v2 = incident.get(j);
+                Collection<T> e_coll = target.findEdge(v1, v2);
+                if (e_coll == null)
+                {
+                    e_coll = new ArrayList<T>();
+                    target.addEdge(e_coll, v1, v2);
+                }
+                e_coll.add(e);
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/VertexPartitionCollapser.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/VertexPartitionCollapser.java
new file mode 100644
index 0000000..dea8444
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/VertexPartitionCollapser.java
@@ -0,0 +1,103 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.transformation;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.blockmodel.VertexPartition;
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * This class transforms a graph with a known vertex partitioning into a graph whose 
+ * vertices correspond to the input graph's partitions.  Two vertices in the output graph
+ * are connected if and only if there exists at least one edge between vertices in the 
+ * corresponding partitions of the input graph.  If the output graph permits parallel edges,
+ * there will be an edge connecting two vertices in the new graph for each such 
+ * edge connecting constituent vertices in the input graph.
+ * 
+ * <p>Concept based on Danyel Fisher's <code>GraphCollapser</code> in JUNG 1.x.
+ * 
+ */
+public class VertexPartitionCollapser<V,E,CV,CE> 
+{
+    protected Supplier<Graph<CV,CE>> graph_factory;
+    protected Supplier<CV> vertex_factory;
+    protected Supplier<CE> edge_factory;
+    protected Map<Set<V>, CV> set_collapsedv;
+    
+    /**
+     * Creates an instance with the specified graph and element factories.
+     * @param vertex_factory used to construct the vertices of the new graph
+     * @param edge_factory used to construct the edges of the new graph
+     * @param graph_factory used to construct the new graph
+     */
+    public VertexPartitionCollapser(Supplier<Graph<CV,CE>> graph_factory, 
+            Supplier<CV> vertex_factory, Supplier<CE> edge_factory)
+    {
+        this.graph_factory = graph_factory;
+        this.vertex_factory = vertex_factory;
+        this.edge_factory = edge_factory;
+        this.set_collapsedv = new HashMap<Set<V>, CV>();
+    }
+
+    /**
+     * Creates a new graph whose vertices correspond to the partitions of the supplied graph.
+     * @param partitioning a vertex partition of a graph
+     * @return a new graph whose vertices correspond to the partitions of the supplied graph
+     */
+    public Graph<CV,CE> collapseVertexPartitions(VertexPartition<V,E> partitioning)
+    {
+        Graph<V,E> original = partitioning.getGraph();
+        Graph<CV, CE> collapsed = graph_factory.get();
+        
+        // create vertices in new graph corresponding to equivalence sets in the original graph
+        for (Set<V> set : partitioning.getVertexPartitions())
+        {
+            CV cv = vertex_factory.get();
+            collapsed.addVertex(vertex_factory.get());
+            set_collapsedv.put(set, cv);
+        }
+
+        // create edges in new graph corresponding to edges in original graph
+        for (E e : original.getEdges())
+        {
+            Collection<V> incident = original.getIncidentVertices(e);
+            Collection<CV> collapsed_vertices = new HashSet<CV>();
+            Map<V, Set<V>> vertex_partitions = partitioning.getVertexToPartitionMap();
+            // collect the collapsed vertices corresponding to the original incident vertices
+            for (V v : incident)
+                collapsed_vertices.add(set_collapsedv.get(vertex_partitions.get(v))); 
+            // if there's only one collapsed vertex, continue (no edges to create)
+            if (collapsed_vertices.size() > 1)
+            {
+                CE ce = edge_factory.get();
+                collapsed.addEdge(ce, collapsed_vertices);
+            }
+        }
+        return collapsed;
+    }
+    
+    /**
+     * @return a Function from vertex sets in the original graph to collapsed vertices
+     * in the transformed graph.
+     */
+    public Function<Set<V>, CV> getSetToCollapsedVertexTransformer()
+    {
+        return Functions.forMap(set_collapsedv);
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/package.html
new file mode 100644
index 0000000..ff67884
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/transformation/package.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Mechanisms for graph transformation.  These currently include:
+<ul>
+<li><code>DirectionTransformer</code>: generates graphs where input undirected 
+edges have been converted to directed edges, or vice versa
+<li><code>FoldingTransformer</code>: transforms k-partite graphs or hypergraphs 
+into unipartite graphs
+<li><code>VertexPartitionCollapser</code>: transforms a graph, given a 
+partition of its vertices into disjoint sets, into a graph in which each 
+of these disjoint sets has been 'collapsed' into a single new vertex.
+</ul>
+
+</body>
+</html>
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/BasicMapEntry.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/BasicMapEntry.java
new file mode 100644
index 0000000..c59ed9b
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/BasicMapEntry.java
@@ -0,0 +1,81 @@
+package edu.uci.ics.jung.algorithms.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An simple minimal implementation of <code>Map.Entry</code>.
+ *
+ * @param <K> the key type
+ * @param <V> the value type
+ */
+public class BasicMapEntry<K,V> implements Map.Entry<K,V> {
+    final K key;
+    V value;
+    
+    /**
+     * @param k the key
+     * @param v the value
+     */
+    public BasicMapEntry(K k, V v) {
+        value = v;
+        key = k;
+    }
+
+    public K getKey() {
+        return key;
+    }
+
+    public V getValue() {
+        return value;
+    }
+
+    public V setValue(V newValue) {
+    V oldValue = value;
+        value = newValue;
+        return oldValue;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Map.Entry))
+            return false;
+        @SuppressWarnings("rawtypes")
+		Map.Entry e = (Map.Entry)o;
+        Object k1 = getKey();
+        Object k2 = e.getKey();
+        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
+            Object v1 = getValue();
+            Object v2 = e.getValue();
+            if (v1 == v2 || (v1 != null && v1.equals(v2))) 
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return (key==null ? 0 : key.hashCode()) ^
+               (value==null   ? 0 : value.hashCode());
+    }
+
+    @Override
+    public String toString() {
+        return getKey() + "=" + getValue();
+    }
+
+    /**
+     * This method is invoked whenever the value in an entry is
+     * overwritten by an invocation of put(k,v) for a key k that's already
+     * in the HashMap.
+     */
+    void recordAccess(HashMap<K,V> m) {
+    }
+
+    /**
+     * This method is invoked whenever the entry is
+     * removed from the table.
+     */
+    void recordRemoval(HashMap<K,V> m) {
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/DiscreteDistribution.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/DiscreteDistribution.java
new file mode 100644
index 0000000..d0ed62a
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/DiscreteDistribution.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ * Created on Feb 18, 2004
+ */
+package edu.uci.ics.jung.algorithms.util;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * A utility class for calculating properties of discrete distributions.
+ * Generally, these distributions are represented as arrays of 
+ * <code>double</code> values, which are assumed to be normalized
+ * such that the entries in a single array sum to 1.  
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class DiscreteDistribution
+{
+
+    /**
+     * Returns the Kullback-Leibler divergence between the 
+     * two specified distributions, which must have the same
+     * number of elements.  This is defined as 
+     * the sum over all <code>i</code> of 
+     * <code>dist[i] * Math.log(dist[i] / reference[i])</code>.
+     * Note that this value is not symmetric; see 
+     * <code>symmetricKL</code> for a symmetric variant. 
+     * @see #symmetricKL(double[], double[])
+     * @param dist the distribution whose divergence from {@code reference} is being measured
+     * @param reference the reference distribution
+     * @return sum_i of {@code dist[i] * Math.log(dist[i] / reference[i])}
+     */
+    public static double KullbackLeibler(double[] dist, double[] reference)
+    {
+        double distance = 0;
+
+        Preconditions.checkArgument(dist.length == reference.length,
+        		"input arrays must be of the same length");
+
+        for (int i = 0; i < dist.length; i++)
+        {
+            if (dist[i] > 0 && reference[i] > 0)
+                distance += dist[i] * Math.log(dist[i] / reference[i]);
+        }
+        return distance;
+    }
+
+    /**
+     * @param dist the distribution whose divergence from {@code reference} is being measured
+     * @param reference the reference distribution
+     * @return <code>KullbackLeibler(dist, reference) + KullbackLeibler(reference, dist)</code>
+     * @see #KullbackLeibler(double[], double[])
+     */
+    public static double symmetricKL(double[] dist, double[] reference)
+    {
+        return KullbackLeibler(dist, reference)
+                + KullbackLeibler(reference, dist);
+    }
+
+    /**
+     * Returns the squared difference between the 
+     * two specified distributions, which must have the same
+     * number of elements.  This is defined as 
+     * the sum over all <code>i</code> of the square of 
+     * <code>(dist[i] - reference[i])</code>.
+     * @param dist the distribution whose distance from {@code reference} is being measured
+     * @param reference the reference distribution
+     * @return sum_i {@code (dist[i] - reference[i])^2}
+     */
+    public static double squaredError(double[] dist, double[] reference)
+    {
+        double error = 0;
+
+        Preconditions.checkArgument(dist.length == reference.length,
+        		"input arrays must be of the same length");
+
+        for (int i = 0; i < dist.length; i++)
+        {
+            double difference = dist[i] - reference[i];
+            error += difference * difference;
+        }
+        return error;
+    }
+
+    /**
+     * Returns the cosine distance between the two 
+     * specified distributions, which must have the same number
+     * of elements.  The distributions are treated as vectors
+     * in <code>dist.length</code>-dimensional space.
+     * Given the following definitions
+     * <ul>
+     * <li><code>v</code> = the sum over all <code>i</code> of <code>dist[i] * dist[i]</code>
+     * <li><code>w</code> = the sum over all <code>i</code> of <code>reference[i] * reference[i]</code>
+     * <li><code>vw</code> = the sum over all <code>i</code> of <code>dist[i] * reference[i]</code>
+     * </ul>
+     * the value returned is defined as <code>vw / (Math.sqrt(v) * Math.sqrt(w))</code>.
+     * @param dist the distribution whose distance from {@code reference} is being measured
+     * @param reference the reference distribution
+     * @return the cosine distance between {@code dist} and {@code reference}, considered as vectors
+     */
+    public static double cosine(double[] dist, double[] reference)
+    {
+        double v_prod = 0; // dot product x*x
+        double w_prod = 0; // dot product y*y
+        double vw_prod = 0; // dot product x*y
+
+        Preconditions.checkArgument(dist.length == reference.length,
+        		"input arrays must be of the same length");
+
+        for (int i = 0; i < dist.length; i++)
+        {
+            vw_prod += dist[i] * reference[i];
+            v_prod += dist[i] * dist[i];
+            w_prod += reference[i] * reference[i];
+        }
+        // cosine distance between v and w
+        return vw_prod / (Math.sqrt(v_prod) * Math.sqrt(w_prod));
+    }
+
+    /**
+     * Returns the entropy of this distribution.
+     * High entropy indicates that the distribution is 
+     * close to uniform; low entropy indicates that the
+     * distribution is close to a Dirac delta (i.e., if
+     * the probability mass is concentrated at a single
+     * point, this method returns 0).  Entropy is defined as 
+     * the sum over all <code>i</code> of 
+     * <code>-(dist[i] * Math.log(dist[i]))</code>
+     * 
+     * @param dist the distribution whose entropy is being measured
+     * @return sum_i {@code -(dist[i] * Math.log(dist[i]))}
+     */
+    public static double entropy(double[] dist)
+    {
+        double total = 0;
+
+        for (int i = 0; i < dist.length; i++)
+        {
+            if (dist[i] > 0)
+                total += dist[i] * Math.log(dist[i]);
+        }
+        return -total;
+    }
+
+    /**
+     * Normalizes, with Lagrangian smoothing, the specified <code>double</code>
+     * array, so that the values sum to 1 (i.e., can be treated as probabilities).
+     * The effect of the Lagrangian smoothing is to ensure that all entries 
+     * are nonzero; effectively, a value of <code>alpha</code> is added to each
+     * entry in the original array prior to normalization.
+     * @param counts the array to be converted into a probability distribution
+     * @param alpha the value to add to each entry prior to normalization
+     */
+    public static void normalize(double[] counts, double alpha)
+    {
+        double total_count = 0;
+
+        for (int i = 0; i < counts.length; i++)
+            total_count += counts[i];
+
+        for (int i = 0; i < counts.length; i++)
+            counts[i] = (counts[i] + alpha)
+                    / (total_count + counts.length * alpha);
+    }
+
+    /**
+     * Returns the mean of the specified <code>Collection</code> of
+     * distributions, which are assumed to be normalized arrays of 
+     * <code>double</code> values.
+     * @see #mean(double[][])
+     * @param distributions the distributions whose mean is to be calculated
+     * @return the mean of the distributions
+     */
+    public static double[] mean(Collection<double[]> distributions)
+    {
+        if (distributions.isEmpty())
+            throw new IllegalArgumentException("Distribution collection must be non-empty");
+        Iterator<double[]> iter = distributions.iterator();
+        double[] first = iter.next();
+        double[][] d_array = new double[distributions.size()][first.length];
+        d_array[0] = first;
+        for (int i = 1; i < d_array.length; i++)
+            d_array[i] = iter.next();
+        
+        return mean(d_array);
+    }
+    
+    /**
+     * Returns the mean of the specified array of distributions,
+     * represented as normalized arrays of <code>double</code> values.
+     * Will throw an "index out of bounds" exception if the 
+     * distribution arrays are not all of the same length.
+     * @param distributions the distributions whose mean is to be calculated
+     * @return the mean of the distributions
+     */
+    public static double[] mean(double[][] distributions)
+    {
+        double[] d_mean = new double[distributions[0].length];
+        for (int j = 0; j < d_mean.length; j++)
+            d_mean[j] = 0;
+            
+        for (int i = 0; i < distributions.length; i++)
+            for (int j = 0; j < d_mean.length; j++)
+                d_mean[j] += distributions[i][j] / distributions.length;
+        
+        return d_mean;
+    }
+    
+}
\ No newline at end of file
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/Indexer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/Indexer.java
new file mode 100644
index 0000000..c41ed45
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/Indexer.java
@@ -0,0 +1,56 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.util;
+
+import java.util.Collection;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+
+/**
+ * A class providing static methods useful for improving the
+ * performance of graph algorithms.
+ * 
+ * @author Tom Nelson
+ *
+ */
+public class Indexer {
+	
+	/**
+	 * Returns a <code>BiMap</code> mapping each element of the collection to its
+	 * index as encountered while iterating over the collection. The purpose
+	 * of the index operation is to supply an O(1) replacement operation for the
+	 * O(n) <code>indexOf(element)</code> method of a <code>List</code>
+	 * @param <T> the type of the collection elements
+	 * @param collection the collection whose indices are to be generated
+	 * @return a bidirectional map from collection elements to 0-based indices
+	 */
+	public static <T> BiMap<T,Integer> create(Collection<T> collection) {
+	    return create(collection, 0);
+	}
+	/**
+	 * Returns a <code>BiMap</code> mapping each element of the collection to its
+	 * index as encountered while iterating over the collection. The purpose
+	 * of the index operation is to supply an O(1) replacement operation for the
+	 * O(n) <code>indexOf(element)</code> method of a <code>List</code>
+	 * @param <T> the type of the collection elements
+	 * @param collection the collection whose indices are to be generated
+	 * @param start start index
+	 * @return a bidirectional map from collection elements to start-based indices
+	 */
+	public static <T> BiMap<T,Integer> create(Collection<T> collection, int start) {
+		BiMap<T,Integer> map = HashBiMap.<T,Integer>create();
+		int i=start;
+		for(T t : collection) {
+			map.put(t,i++);
+		}
+		return map;
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/IterativeContext.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/IterativeContext.java
new file mode 100644
index 0000000..de71962
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/IterativeContext.java
@@ -0,0 +1,28 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.util;
+
+
+/**
+ * An interface for algorithms that proceed iteratively.
+ *
+ */
+public interface IterativeContext 
+{
+	/**
+	 * Advances one step.
+	 */
+	void step();
+
+	/**
+	 * @return {@code true} if this iterative process is finished, and {@code false} otherwise.
+	 */
+	boolean done();
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/IterativeProcess.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/IterativeProcess.java
new file mode 100644
index 0000000..67749e7
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/IterativeProcess.java
@@ -0,0 +1,174 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.util;
+
+
+
+/**
+ * Provides basic infrastructure for iterative algorithms. Services provided include:
+ * <ul>
+ * <li> storage of current and max iteration count </li>
+ * <li> framework for initialization, iterative evaluation, and finalization </li>
+ * <li> test for convergence </li>
+ * <li> etc. </li>
+ * </ul>
+ * <p>
+ * Algorithms that subclass this class are typically used in the following way: <br>
+ * <pre>
+ * FooAlgorithm foo = new FooAlgorithm(...)
+ * foo.setMaximumIterations(100); //set up conditions
+ * ...
+ * foo.evaluate(); //key method which initiates iterative process
+ * foo.getSomeResult();
+ * </pre>
+ * 
+ * @author Scott White (originally written by Didier Besset)
+ */
+public abstract class IterativeProcess implements IterativeContext {
+    /**
+     * Number of iterations performed.
+     */
+    private int iterations;
+    /**
+     * Maximum allowed number of iterations.
+     */
+    private int maximumIterations = 50;
+    /**
+     * Desired precision.
+     */
+    private double desiredPrecision = Double.MIN_VALUE;
+    /**
+     * Achieved precision.
+     */
+    private double precision;
+
+
+    /**
+     * Generic constructor.
+     */
+    public IterativeProcess() {
+    }
+
+    /**
+     * Performs the iterative process.
+     * Note: this method does not return anything because Java does not
+     * allow mixing double, int, or objects
+     */
+    public void evaluate() {
+        iterations = 0;
+        initializeIterations();
+        while (iterations++ < maximumIterations) {
+        	step();
+            precision = getPrecision();
+            if (hasConverged())
+                break;
+        }
+        finalizeIterations();
+    }
+
+    /**
+     * Evaluate the result of the current iteration.
+     */
+    abstract public void step();
+
+    /**
+     * Perform eventual clean-up operations
+     * (must be implement by subclass when needed).
+     */
+    protected void finalizeIterations() {
+    }
+
+    /**
+     * @return the desired precision.
+     */
+    public double getDesiredPrecision() {
+        return desiredPrecision;
+    }
+
+    /**
+     * @return the number of iterations performed.
+     */
+    public int getIterations() {
+        return iterations;
+    }
+
+    /**
+     * @return the maximum allowed number of iterations.
+     */
+    public int getMaximumIterations() {
+        return maximumIterations;
+    }
+
+    /**
+     * @return the attained precision.
+     */
+    public double getPrecision() {
+        return precision;
+    }
+
+    /**
+	 * @param precision the precision to set
+	 */
+	public void setPrecision(double precision) {
+		this.precision = precision;
+	}
+
+	/**
+     *
+     * Check to see if the result has been attained.
+     * @return boolean
+     */
+    public boolean hasConverged() {
+        return precision < desiredPrecision;
+    }
+    
+    public boolean done() {
+    	return hasConverged();
+    }
+
+    /**
+     * Initializes internal parameters to start the iterative process.
+     */
+    protected void initializeIterations() {
+    }
+
+    /**
+     * 
+     */
+    public void reset() {
+    }
+
+    /**
+     * @return double
+     * @param epsilon double
+     * @param x double
+     */
+    public double relativePrecision(double epsilon, double x) {
+        return x > desiredPrecision ? epsilon / x: epsilon;
+    }
+
+    /**
+     * @param prec the desired precision.
+     */
+    public void setDesiredPrecision(double prec) throws IllegalArgumentException {
+        if (prec <= 0)
+            throw new IllegalArgumentException("Non-positive precision: " + prec);
+        desiredPrecision = prec;
+    }
+
+    /**
+     * @param maxIter the maximum allowed number of iterations
+     */
+    public void setMaximumIterations(int maxIter) throws IllegalArgumentException {
+        if (maxIter < 1)
+            throw new IllegalArgumentException("Non-positive maximum iteration: " + maxIter);
+        maximumIterations = maxIter;
+    }
+}
\ No newline at end of file
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/KMeansClusterer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/KMeansClusterer.java
new file mode 100644
index 0000000..bfc7e52
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/KMeansClusterer.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Aug 9, 2004
+ *
+ */
+package edu.uci.ics.jung.algorithms.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+
+
+/**
+ * Groups items into a specified number of clusters, based on their proximity in
+ * d-dimensional space, using the k-means algorithm. Calls to
+ * <code>cluster</code> will terminate when either of the two following
+ * conditions is true:
+ * <ul>
+ * <li>the number of iterations is > <code>max_iterations</code> 
+ * <li>none of the centroids has moved as much as <code>convergence_threshold</code>
+ * since the previous iteration
+ * </ul>
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class KMeansClusterer<T>
+{
+    protected int max_iterations;
+    protected double convergence_threshold;
+    protected Random rand;
+
+    /**
+     * Creates an instance which will terminate when either the maximum number of 
+     * iterations has been reached, or all changes are smaller than the convergence threshold.
+     * @param max_iterations the maximum number of iterations to employ
+     * @param convergence_threshold the smallest change we want to track
+     */
+    public KMeansClusterer(int max_iterations, double convergence_threshold)
+    {
+        this.max_iterations = max_iterations;
+        this.convergence_threshold = convergence_threshold;
+        this.rand = new Random();
+    }
+
+    /**
+     * Creates an instance with max iterations of 100 and convergence threshold
+     * of 0.001.
+     */
+    public KMeansClusterer()
+    {
+        this(100, 0.001);
+    }
+
+    /**
+     * @return the maximum number of iterations
+     */
+    public int getMaxIterations()
+    {
+        return max_iterations;
+    }
+
+    /**
+     * @param max_iterations the maximum number of iterations
+     */
+    public void setMaxIterations(int max_iterations)
+    {
+        if (max_iterations < 0)
+            throw new IllegalArgumentException("max iterations must be >= 0");
+
+        this.max_iterations = max_iterations;
+    }
+
+    /**
+     * @return the convergence threshold
+     */
+    public double getConvergenceThreshold()
+    {
+        return convergence_threshold;
+    }
+
+    /**
+     * @param convergence_threshold the convergence threshold
+     */
+    public void setConvergenceThreshold(double convergence_threshold)
+    {
+        if (convergence_threshold <= 0)
+            throw new IllegalArgumentException("convergence threshold " +
+                "must be > 0");
+
+        this.convergence_threshold = convergence_threshold;
+    }
+
+    /**
+     * Returns a <code>Collection</code> of clusters, where each cluster is
+     * represented as a <code>Map</code> of <code>Objects</code> to locations
+     * in d-dimensional space.
+     * @param object_locations  a map of the items to cluster, to
+     * <code>double</code> arrays that specify their locations in d-dimensional space.
+     * @param num_clusters  the number of clusters to create
+     * @return a clustering of the input objects in d-dimensional space
+     * @throws NotEnoughClustersException if {@code num_clusters} is larger than the number of
+     *     distinct points in object_locations
+     */
+    @SuppressWarnings("unchecked")
+    public Collection<Map<T, double[]>> cluster(Map<T, double[]> object_locations, int num_clusters)
+    {
+        if (object_locations == null || object_locations.isEmpty())
+            throw new IllegalArgumentException("'objects' must be non-empty");
+
+        if (num_clusters < 2 || num_clusters > object_locations.size())
+            throw new IllegalArgumentException("number of clusters " +
+                "must be >= 2 and <= number of objects (" +
+                object_locations.size() + ")");
+
+
+        Set<double[]> centroids = new HashSet<double[]>();
+
+        Object[] obj_array = object_locations.keySet().toArray();
+        Set<T> tried = new HashSet<T>();
+
+        // create the specified number of clusters
+        while (centroids.size() < num_clusters && tried.size() < object_locations.size())
+        {
+            T o = (T)obj_array[(int)(rand.nextDouble() * obj_array.length)];
+            tried.add(o);
+            double[] mean_value = object_locations.get(o);
+            boolean duplicate = false;
+            for (double[] cur : centroids)
+            {
+                if (Arrays.equals(mean_value, cur))
+                    duplicate = true;
+            }
+            if (!duplicate)
+                centroids.add(mean_value);
+        }
+
+        if (tried.size() >= object_locations.size())
+            throw new NotEnoughClustersException();
+
+        // put items in their initial clusters
+        Map<double[], Map<T, double[]>> clusterMap = assignToClusters(object_locations, centroids);
+
+        // keep reconstituting clusters until either
+        // (a) membership is stable, or
+        // (b) number of iterations passes max_iterations, or
+        // (c) max movement of any centroid is <= convergence_threshold
+        int iterations = 0;
+        double max_movement = Double.POSITIVE_INFINITY;
+        while (iterations++ < max_iterations && max_movement > convergence_threshold)
+        {
+            max_movement = 0;
+            Set<double[]> new_centroids = new HashSet<double[]>();
+            // calculate new mean for each cluster
+            for (Map.Entry<double[], Map<T, double[]>> entry : clusterMap.entrySet())
+            {
+                double[] centroid = entry.getKey();
+                Map<T, double[]> elements = entry.getValue();
+                ArrayList<double[]> locations = new ArrayList<double[]>(elements.values());
+
+                double[] mean = DiscreteDistribution.mean(locations);
+                max_movement = Math.max(max_movement,
+                    Math.sqrt(DiscreteDistribution.squaredError(centroid, mean)));
+                new_centroids.add(mean);
+            }
+
+            // TODO: check membership of clusters: have they changed?
+
+            // regenerate cluster membership based on means
+            clusterMap = assignToClusters(object_locations, new_centroids);
+        }
+        return clusterMap.values();
+    }
+
+    /**
+     * Assigns each object to the cluster whose centroid is closest to the
+     * object.
+     * @param object_locations  a map of objects to locations
+     * @param centroids         the centroids of the clusters to be formed
+     * @return a map of objects to assigned clusters
+     */
+    protected Map<double[], Map<T, double[]>> assignToClusters(Map<T, double[]> object_locations, Set<double[]> centroids)
+    {
+        Map<double[], Map<T, double[]>> clusterMap = new HashMap<double[], Map<T, double[]>>();
+        for (double[] centroid : centroids)
+            clusterMap.put(centroid, new HashMap<T, double[]>());
+
+        for (Map.Entry<T, double[]> object_location : object_locations.entrySet())
+        {
+            T object = object_location.getKey();
+            double[] location = object_location.getValue();
+
+            // find the cluster with the closest centroid
+            Iterator<double[]> c_iter = centroids.iterator();
+            double[] closest = c_iter.next();
+            double distance = DiscreteDistribution.squaredError(location, closest);
+
+            while (c_iter.hasNext())
+            {
+                double[] centroid = c_iter.next();
+                double dist_cur = DiscreteDistribution.squaredError(location, centroid);
+                if (dist_cur < distance)
+                {
+                    distance = dist_cur;
+                    closest = centroid;
+                }
+            }
+            clusterMap.get(closest).put(object, location);
+        }
+
+        return clusterMap;
+    }
+
+    /**
+     * Sets the seed used by the internal random number generator.
+     * Enables consistent outputs.
+     * @param random_seed the random seed to use
+     */
+    public void setSeed(int random_seed)
+    {
+        this.rand = new Random(random_seed);
+    }
+
+    /**
+     * An exception that indicates that the specified data points cannot be
+     * clustered into the number of clusters requested by the user.
+     * This will happen if and only if there are fewer distinct points than
+     * requested clusters.  (If there are fewer total data points than
+     * requested clusters, <code>IllegalArgumentException</code> will be thrown.)
+     *
+     * @author Joshua O'Madadhain
+     */
+    @SuppressWarnings("serial")
+    public static class NotEnoughClustersException extends RuntimeException
+    {
+        @Override
+        public String getMessage()
+        {
+            return "Not enough distinct points in the input data set to form " +
+                    "the requested number of clusters";
+        }
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/MapBinaryHeap.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/MapBinaryHeap.java
new file mode 100644
index 0000000..cf152de
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/MapBinaryHeap.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * 
+ * Created on Oct 29, 2003
+ */
+package edu.uci.ics.jung.algorithms.util;
+
+import java.util.AbstractCollection;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+import java.util.Vector;
+
+import com.google.common.collect.Iterators;
+
+/**
+ * An array-based binary heap implementation of a priority queue, 
+ * which also provides
+ * efficient <code>update()</code> and <code>contains</code> operations.
+ * It contains extra infrastructure (a hash table) to keep track of the 
+ * position of each element in the array; thus, if the key value of an element
+ * changes, it may be "resubmitted" to the heap via <code>update</code>
+ * so that the heap can reposition it efficiently, as necessary.  
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class MapBinaryHeap<T>
+    extends AbstractCollection<T> 
+    implements Queue<T>
+{
+	private Vector<T> heap = new Vector<T>();            // holds the heap as an implicit binary tree
+    private Map<T,Integer> object_indices = new HashMap<T,Integer>(); // maps each object in the heap to its index in the heap
+    private Comparator<T> comp;
+    private final static int TOP = 0;   // the index of the top of the heap
+
+    /**
+     * Creates a <code>MapBinaryHeap</code> whose heap ordering
+     * is based on the ordering of the elements specified by <code>comp</code>.
+     * @param comp the comparator to use to order elements in the heap
+     */
+    public MapBinaryHeap(Comparator<T> comp)
+    {
+        initialize(comp);
+    }
+    
+    /**
+     * Creates a <code>MapBinaryHeap</code> whose heap ordering
+     * will be based on the <i>natural ordering</i> of the elements,
+     * which must be <code>Comparable</code>.
+     */
+    public MapBinaryHeap()
+    {
+        initialize(new ComparableComparator());
+    }
+
+    /**
+     * Creates a <code>MapBinaryHeap</code> based on the specified
+     * collection whose heap ordering
+     * will be based on the <i>natural ordering</i> of the elements,
+     * which must be <code>Comparable</code>.
+     * @param c the collection of {@code Comparable} elements to add to the heap
+     */
+    public MapBinaryHeap(Collection<T> c)
+    {
+    	this();
+        addAll(c);
+    }
+    
+    /**
+     * Creates a <code>MapBinaryHeap</code> based on the specified collection 
+     * whose heap ordering
+     * is based on the ordering of the elements specified by <code>c</code>.
+     * @param c the collection of elements to add to the heap
+     * @param comp the comparator to use for items in {@code c}
+     */
+    public MapBinaryHeap(Collection<T> c, Comparator<T> comp)
+    {
+        this(comp);
+        addAll(c);
+    }
+    
+    private void initialize(Comparator<T> comp)
+    {
+        this.comp = comp;
+        clear();
+    }
+    
+	/**
+	 * @see Collection#clear()
+	 */
+	@Override
+	public void clear()
+	{
+        object_indices.clear();
+        heap.clear();
+	}
+
+	/**
+	 * Inserts <code>o</code> into this collection.
+	 */
+	@Override
+	public boolean add(T o)
+	{
+        int i = heap.size();  // index 1 past the end of the heap
+        heap.setSize(i+1);
+        percolateUp(i, o);
+        return true;
+	}
+
+	/**
+	 * Returns <code>true</code> if this collection contains no elements, and
+     * <code>false</code> otherwise.
+	 */
+	@Override
+	public boolean isEmpty()
+	{
+        return heap.isEmpty();
+	}
+
+	/**
+	 * Returns the element at the top of the heap; does not
+     * alter the heap.
+	 */
+	public T peek()
+	{
+		if (heap.size() > 0)
+			return heap.elementAt(TOP);
+		else
+			return null;
+	}
+
+    /**
+     * @return the size of this heap
+     */
+    @Override
+    public int size() 
+    {
+        return heap.size();
+    }
+       
+    /**
+     * Informs the heap that this object's internal key value has been
+     * updated, and that its place in the heap may need to be shifted
+     * (up or down).
+     * @param o the object whose key value has been updated
+     */
+    public void update(T o)
+    {
+        // Since we don't know whether the key value increased or 
+        // decreased, we just percolate up followed by percolating down;
+        // one of the two will have no effect.
+        
+        int cur = object_indices.get(o).intValue(); // current index
+        int new_idx = percolateUp(cur, o);
+        percolateDown(new_idx);
+    }
+
+    @Override
+    public boolean contains(Object o)
+    {
+        return object_indices.containsKey(o);
+    }
+    
+    /**
+     * Moves the element at position <code>cur</code> closer to 
+     * the bottom of the heap, or returns if no further motion is
+     * necessary.  Calls itself recursively if further motion is 
+     * possible.
+     */
+    private void percolateDown(int cur)
+    {
+        int left = lChild(cur);
+        int right = rChild(cur);
+        int smallest;
+
+        if ((left < heap.size()) && 
+        		(comp.compare(heap.elementAt(left), heap.elementAt(cur)) < 0)) {
+			smallest = left;
+		} else {
+			smallest = cur;
+		}
+
+        if ((right < heap.size()) && 
+        		(comp.compare(heap.elementAt(right), heap.elementAt(smallest)) < 0)) {
+			smallest = right;
+		}
+
+        if (cur != smallest)
+        {
+            swap(cur, smallest);
+            percolateDown(smallest);
+        }
+    }
+
+    /**
+     * Moves the element <code>o</code> at position <code>cur</code> 
+     * as high as it can go in the heap.  Returns the new position of the 
+     * element in the heap.
+     */
+    private int percolateUp(int cur, T o)
+    {
+        int i = cur;
+        
+        while ((i > TOP) && (comp.compare(heap.elementAt(parent(i)), o) > 0))
+        {
+            T parentElt = heap.elementAt(parent(i));
+            heap.setElementAt(parentElt, i);
+            object_indices.put(parentElt, new Integer(i));  // reset index to i (new location)
+            i = parent(i);
+        }
+        
+        // place object in heap at appropriate place
+        object_indices.put(o, new Integer(i));
+        heap.setElementAt(o, i);
+
+        return i;
+    }
+    
+    /**
+     * Returns the index of the left child of the element at 
+     * index <code>i</code> of the heap.
+     * @param i
+     * @return the index of the left child of the element at 
+     * index <code>i</code> of the heap
+     */
+    private int lChild(int i)
+    {
+    	return (i<<1) + 1;
+    }
+    
+    /**
+     * Returns the index of the right child of the element at 
+     * index <code>i</code> of the heap.
+     * @param i
+     * @return the index of the right child of the element at 
+     * index <code>i</code> of the heap
+     */
+    private int rChild(int i)
+    {
+    	return (i<<1) + 2;
+    }
+    
+    /**
+     * Returns the index of the parent of the element at 
+     * index <code>i</code> of the heap.
+     * @param i
+     * @return the index of the parent of the element at index i of the heap
+     */
+    private int parent(int i)
+    {
+    	return (i-1)>>1;
+    }
+    
+    /**
+     * Swaps the positions of the elements at indices <code>i</code>
+     * and <code>j</code> of the heap.
+     * @param i
+     * @param j
+     */
+    private void swap(int i, int j)
+    {
+        T iElt = heap.elementAt(i);
+        T jElt = heap.elementAt(j);
+
+        heap.setElementAt(jElt, i);
+        object_indices.put(jElt, new Integer(i));
+
+        heap.setElementAt(iElt, j);
+        object_indices.put(iElt, new Integer(j));
+    }
+    
+    /**
+     * Comparator used if none is specified in the constructor.
+     * @author Joshua O'Madadhain
+     */
+    private class ComparableComparator implements Comparator<T>
+    {
+        /**
+         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
+         */
+        @SuppressWarnings("unchecked")
+        public int compare(T arg0, T arg1)
+        {
+            if (!(arg0 instanceof Comparable) || !(arg1 instanceof Comparable))
+                throw new IllegalArgumentException("Arguments must be Comparable");
+            
+            return ((Comparable<T>)arg0).compareTo(arg1);
+        }
+    }
+
+    /**
+     * Returns an <code>Iterator</code> that does not support modification
+     * of the heap.
+     */
+    @Override
+    public Iterator<T> iterator()
+    {
+        return Iterators.<T>unmodifiableIterator(heap.iterator());
+    }
+
+    /**
+     * This data structure does not support the removal of arbitrary elements.
+     */
+    @Override
+    public boolean remove(Object o)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * This data structure does not support the removal of arbitrary elements.
+     */
+    @Override
+    public boolean removeAll(Collection<?> c)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * This data structure does not support the removal of arbitrary elements.
+     */
+    @Override
+    public boolean retainAll(Collection<?> c)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+	public T element() throws NoSuchElementException 
+	{
+		T top = this.peek();
+		if (top == null) 
+			throw new NoSuchElementException();
+		return top;
+	}
+
+	public boolean offer(T o) 
+	{
+		return add(o);
+	}
+
+	public T poll() 
+	{
+        T top = this.peek();
+        if (top != null)
+        {
+	        T bottom_elt = heap.lastElement();
+	        heap.setElementAt(bottom_elt, TOP);
+	        object_indices.put(bottom_elt, new Integer(TOP));
+	        
+	        heap.setSize(heap.size() - 1);  // remove the last element
+	        if (heap.size() > 1)
+	        	percolateDown(TOP);
+	
+	        object_indices.remove(top);
+        }
+        return top;
+	}
+
+	public T remove() 
+	{
+		T top = this.poll();
+		if (top == null)
+			throw new NoSuchElementException();
+		return top;
+	}
+
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/MapSettableTransformer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/MapSettableTransformer.java
new file mode 100644
index 0000000..267ea61
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/MapSettableTransformer.java
@@ -0,0 +1,45 @@
+/*
+ * Created on Aug 5, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.util;
+
+import java.util.Map;
+
+
+/**
+ * A <code>SettableTransformer</code> that operates on an underlying <code>Map</code> instance.
+ * Similar to <code>MapTransformer</code>.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class MapSettableTransformer<I, O> implements SettableTransformer<I, O>
+{
+    protected Map<I,O> map;
+    
+    /**
+     * Creates an instance based on <code>m</code>.
+     * @param m the map on which this instance is based
+     */
+    public MapSettableTransformer(Map<I,O> m)
+    {
+        this.map = m;
+    }
+
+    public O apply(I input)
+    {
+        return map.get(input);
+    }
+
+    public void set(I input, O output)
+    {
+        map.put(input, output);
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/SelfLoopEdgePredicate.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/SelfLoopEdgePredicate.java
new file mode 100644
index 0000000..13d9044
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/SelfLoopEdgePredicate.java
@@ -0,0 +1,23 @@
+package edu.uci.ics.jung.algorithms.util;
+
+import com.google.common.base.Predicate;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * A <code>Predicate</code> that returns <code>true</code> if the input edge's 
+ * endpoints in the input graph are identical.  (Thus, an edge which connects
+ * its sole incident vertex to itself).
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+public class SelfLoopEdgePredicate<V,E> implements Predicate<Context<Graph<V,E>,E>> {
+
+    public boolean apply(Context<Graph<V,E>,E> context) {
+        Pair<V> endpoints = context.graph.getEndpoints(context.element);
+        return endpoints.getFirst().equals(endpoints.getSecond());
+    }
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/SettableTransformer.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/SettableTransformer.java
new file mode 100644
index 0000000..dc0797b
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/SettableTransformer.java
@@ -0,0 +1,31 @@
+/*
+ * Created on Aug 5, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.util;
+
+import com.google.common.base.Function;
+
+/**
+ * An interface for classes that can set the value to be returned (from <code>transform()</code>)
+ * when invoked on a given input.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public interface SettableTransformer<I, O> extends Function<I, O>
+{
+    /**
+     * Sets the value (<code>output</code>) to be returned by a call to 
+     * <code>transform(input)</code>).
+     * @param input the value whose output value is being specified
+     * @param output the output value for {@code input}
+     */
+    public void set(I input, O output);
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/WeightedChoice.java b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/WeightedChoice.java
new file mode 100644
index 0000000..719caef
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/WeightedChoice.java
@@ -0,0 +1,203 @@
+/**
+ * Copyright (c) 2009, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Jan 8, 2009
+ * 
+ */
+package edu.uci.ics.jung.algorithms.util;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Random;
+
+/**
+ * Selects items according to their probability in an arbitrary probability 
+ * distribution.  The distribution is specified by a {@code Map} from
+ * items (of type {@code T}) to weights of type {@code Number}, supplied
+ * to the constructor; these weights are normalized internally to act as 
+ * probabilities.
+ * 
+ * <p>This implementation selects items in O(1) time, and requires O(n) space.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class WeightedChoice<T> 
+{
+	private List<ItemPair> item_pairs;
+	private Random random;
+	
+	/**
+	 * The default minimum value that is treated as a valid probability 
+	 * (as opposed to rounding error from floating-point operations). 
+	 */
+	public static final double DEFAULT_THRESHOLD = 0.00000000001;
+
+	/**
+	 * Equivalent to {@code this(item_weights, new Random(), DEFAULT_THRESHOLD)}.
+	 * @param item_weights a map from items to their weights
+	 */
+	public WeightedChoice(Map<T, ? extends Number> item_weights)
+	{
+		this(item_weights, new Random(), DEFAULT_THRESHOLD);
+	}
+
+	/**
+	 * Equivalent to {@code this(item_weights, new Random(), threshold)}.
+	 * @param item_weights a map from items to their weights
+	 * @param threshold the minimum value that is treated as a probability
+	 *     (anything smaller will be considered equivalent to a floating-point rounding error)
+	 */
+	public WeightedChoice(Map<T, ? extends Number> item_weights, double threshold)
+	{
+		this(item_weights, new Random(), threshold);
+	}
+	
+	/**
+	 * Equivalent to {@code this(item_weights, random, DEFAULT_THRESHOLD)}.
+	 * @param item_weights a map from items to their weights
+	 * @param random the Random instance to use for selection
+	 */
+	public WeightedChoice(Map<T, ? extends Number> item_weights, Random random)
+	{
+		this(item_weights, random, DEFAULT_THRESHOLD);
+	}
+	
+	/**
+	 * Creates an instance with the specified mapping from items to weights,
+	 * random number generator, and threshold value.
+	 * 
+	 * <p>The mapping defines the weight for each item to be selected; this 
+	 * will be proportional to the probability of its selection.
+	 * <p>The random number generator specifies the mechanism which will be
+	 * used to provide uniform integer and double values.
+	 * <p>The threshold indicates default minimum value that is treated as a valid 
+	 * probability (as opposed to rounding error from floating-point operations). 
+	 * @param item_weights a map from items to their weights
+	 * @param random the Random instance to use for selection
+	 * @param threshold the minimum value that is treated as a probability
+	 *     (anything smaller will be considered equivalent to a floating-point rounding error)
+	 */
+	public WeightedChoice(Map<T, ? extends Number> item_weights, Random random,
+			double threshold) 
+	{
+		if (item_weights.isEmpty())
+			throw new IllegalArgumentException("Item weights must be non-empty");
+		
+		int item_count = item_weights.size();
+		item_pairs = new ArrayList<ItemPair>(item_count);
+		
+		double sum = 0;
+		for (Map.Entry<T, ? extends Number> entry : item_weights.entrySet())
+		{
+			double value = entry.getValue().doubleValue();
+			if (value <= 0)
+				throw new IllegalArgumentException("Weights must be > 0");
+			sum += value;
+		}
+        double bucket_weight = 1.0 / item_weights.size();
+		
+		Queue<ItemPair> light_weights = new LinkedList<ItemPair>();
+		Queue<ItemPair> heavy_weights = new LinkedList<ItemPair>();
+		for (Map.Entry<T, ? extends Number> entry : item_weights.entrySet())
+		{
+			double value = entry.getValue().doubleValue() / sum;
+			enqueueItem(entry.getKey(), value, bucket_weight, light_weights, heavy_weights);
+		}
+		
+		// repeat until both queues empty
+		while (!heavy_weights.isEmpty() || !light_weights.isEmpty())
+		{
+			ItemPair heavy_item = heavy_weights.poll();
+			ItemPair light_item = light_weights.poll();
+			double light_weight = 0;
+			T light = null;
+			T heavy = null;
+			if (light_item != null)
+			{
+				light_weight = light_item.weight;
+				light = light_item.light;
+			}
+			if (heavy_item != null)
+			{
+				heavy = heavy_item.heavy;
+				// put the 'left over' weight from the heavy item--what wasn't
+				// needed to make up the difference between the light weight and
+				// 1/n--back in the appropriate queue
+				double new_weight = heavy_item.weight - (bucket_weight - light_weight);
+				if (new_weight > threshold)
+					enqueueItem(heavy, new_weight, bucket_weight, light_weights, heavy_weights);
+			}
+			light_weight *= item_count;
+			
+			item_pairs.add(new ItemPair(light, heavy, light_weight));
+		}
+		
+		this.random = random;
+	}
+
+	/**
+	 * Adds key/value to the appropriate queue.  Keys with values less than
+	 * the threshold get added to {@code light_weights}, all others get added
+	 * to {@code heavy_weights}.
+	 */
+	private void enqueueItem(T key, double value, double threshold, 
+			Queue<ItemPair> light_weights, Queue<ItemPair> heavy_weights)
+	{
+		if (value < threshold) 
+			light_weights.offer(new ItemPair(key, null, value));
+		else
+			heavy_weights.offer(new ItemPair(null, key, value));
+	}
+	
+	/**
+	 * @param seed the seed to be used by the internal random number generator
+	 */
+	public void setRandomSeed(long seed)
+	{
+		this.random.setSeed(seed);
+	}
+	
+	/**
+	 * Retrieves an item with probability proportional to its weight in the
+	 * {@code Map} provided in the input.
+	 * @return an item chosen randomly based on its specified weight
+	 */
+	public T nextItem()
+	{
+		ItemPair item_pair = item_pairs.get(random.nextInt(item_pairs.size()));
+		if (random.nextDouble() < item_pair.weight)
+			return item_pair.light;
+		return item_pair.heavy;
+	}
+	
+	/**
+	 * Manages light object/heavy object/light conditional probability tuples.
+	 */
+	private class ItemPair 
+	{
+		T light;
+		T heavy;
+		double weight;
+		
+		private ItemPair(T light, T heavy, double weight)
+		{
+			this.light = light;
+			this.heavy = heavy;
+			this.weight = weight;
+		}
+		
+		@Override
+        public String toString()
+		{
+			return String.format("[L:%s, H:%s, %.3f]", light, heavy, weight);
+		}
+	}
+}
diff --git a/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/package.html b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/package.html
new file mode 100644
index 0000000..dc82ebe
--- /dev/null
+++ b/jung-algorithms/src/main/java/edu/uci/ics/jung/algorithms/util/package.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+Provides general algorithmic utilities.  These include:
+<ul>
+<li><code>DiscreteDistribution</code>: calculates statistical measures on 
+discrete probability distributions represented as <code>double</code> arrays
+<li><code>KMeansClusterer</code>: uses the k-means algorithm to cluster 
+points in d-dimensional space into k clusters 
+<li><code>MapBinaryHeap</code>: a binary heap implementation that permits
+efficient element access and update operations
+<li><code>RandomLocationTransformer</code>: a class that randomly assigns
+2D coordinates to items (default initializer for iterative Layouts)
+<li><code>SettableTransformer</code>: an extension of <code>Transformer</code>
+that allows mutation of the transformation
+</ul>
+
+</body>
+</html>
diff --git a/jung-algorithms/src/site/site.xml b/jung-algorithms/src/site/site.xml
new file mode 100644
index 0000000..4f25bdf
--- /dev/null
+++ b/jung-algorithms/src/site/site.xml
@@ -0,0 +1,14 @@
+<project name="${project.name}">
+  <bannerLeft>
+    <name>${project.name}</name>
+  </bannerLeft>
+  <body>
+    <links>
+      <item name="${project.name}" href="${project.url}"/>
+    </links>
+    <menu ref="parent" />
+    <menu ref="modules" />
+    <menu ref="reports" />
+  </body>
+</project>
+
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/cluster/TestBicomponentClusterer.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/cluster/TestBicomponentClusterer.java
new file mode 100644
index 0000000..dafc559
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/cluster/TestBicomponentClusterer.java
@@ -0,0 +1,268 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.cluster;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.UndirectedSparseMultigraph;
+
+
+/**
+ * @author Scott White
+ */
+public class TestBicomponentClusterer extends TestCase {
+	public static Test suite() {
+		return new TestSuite(TestBicomponentClusterer.class);
+	}
+
+	@Override
+  protected void setUp() {
+
+	}
+
+    public void testExtract0() throws Exception
+    {
+        UndirectedGraph<String,Number> graph = new UndirectedSparseMultigraph<String,Number>();
+        String[] v = {"0"};
+        graph.addVertex(v[0]);
+        
+        List<Set<String>> c = new ArrayList<Set<String>>();
+        c.add(0, new HashSet<String>());
+        c.get(0).add(v[0]);
+        
+//        Set[] c = {new HashSet<String>()};
+        
+//        c[0].add(v[0]);
+     
+        testComponents(graph, v, c);
+    }
+
+    public void testExtractEdge() throws Exception
+    {
+        UndirectedGraph<String,Number> graph = new UndirectedSparseMultigraph<String,Number>();
+        String[] v = {"0","1"}; 
+        graph.addVertex(v[0]);
+        graph.addVertex(v[1]);
+        graph.addEdge(0, v[0], v[1]);
+        
+        List<Set<String>> c = new ArrayList<Set<String>>();
+        c.add(0, new HashSet<String>());
+        c.get(0).add(v[0]);
+        c.get(0).add(v[1]);
+
+//        Set[] c = {new HashSet()};
+//        
+//        c[0].add(v[0]);
+//        c[0].add(v[1]);
+     
+        testComponents(graph, v, c);
+    }
+    
+    public void testExtractV() throws Exception
+    {
+        UndirectedGraph<String,Number> graph = new UndirectedSparseMultigraph<String,Number>();
+        String[] v = new String[3];
+        for (int i = 0; i < 3; i++)
+        {
+            v[i] = ""+i;
+            graph.addVertex(v[i]);
+        }
+        graph.addEdge(0, v[0], v[1]);
+        graph.addEdge(1, v[0], v[2]);
+        
+        List<Set<String>> c = new ArrayList<Set<String>>();
+        c.add(0, new HashSet<String>());
+        c.add(1, new HashSet<String>());
+
+        c.get(0).add(v[0]);
+        c.get(0).add(v[1]);
+
+        c.get(1).add(v[0]);
+        c.get(1).add(v[2]);
+        
+//        Set[] c = {new HashSet(), new HashSet()};
+//              
+//        c[0].add(v[0]);
+//        c[0].add(v[1]);
+//        
+//        c[1].add(v[0]);
+//        c[1].add(v[2]);
+           
+        testComponents(graph, v, c);
+    }
+    
+    public void createEdges(String[] v, int[][] edge_array, Graph<String,Number> g)
+    {
+        for (int k = 0; k < edge_array.length; k++)
+        {
+            int i = edge_array[k][0];
+            int j = edge_array[k][1];
+            String v1 = getVertex(v, i, g);
+            String v2 = getVertex(v, j, g);
+            
+            g.addEdge(k, v1, v2);
+        }
+    }
+    
+    public String getVertex(String[] v_array, int i, Graph<String,Number> g)
+    {
+        String v = v_array[i];
+        if (v == null)
+        {
+        	v_array[i] = Character.toString((char)('0'+i));
+            g.addVertex(v_array[i]);
+            v = v_array[i];
+        }
+        return v;
+    }
+    
+	public void testExtract1() {
+        String[] v = new String[6];
+        int[][] edges1 = {{0,1}, {0,5}, {0,3}, {0,4}, {1,5}, {3,4}, {2,3}};
+        UndirectedGraph<String,Number> graph = new UndirectedSparseMultigraph<String,Number>();
+        createEdges(v, edges1, graph);
+        
+        List<Set<String>> c = new ArrayList<Set<String>>();
+        for (int i = 0; i < 3; i++)
+            c.add(i, new HashSet<String>());
+
+        c.get(0).add(v[0]);
+        c.get(0).add(v[1]);
+        c.get(0).add(v[5]);
+
+        c.get(1).add(v[0]);
+        c.get(1).add(v[3]);
+        c.get(1).add(v[4]);
+
+        c.get(2).add(v[2]);
+        c.get(2).add(v[3]);
+
+//        Set[] c = new Set[3];
+//        for (int i = 0; i < c.length; i++)
+//            c[i] = new HashSet();
+//        
+//        c[0].add(v[0]);
+//        c[0].add(v[1]);
+//        c[0].add(v[5]);
+//        
+//        c[1].add(v[0]);
+//        c[1].add(v[3]);
+//        c[1].add(v[4]);
+//        
+//        c[2].add(v[2]);
+//        c[2].add(v[3]);
+        
+        testComponents(graph, v, c);
+	}
+    
+    public void testExtract2() {
+        String[] v = new String[9];
+        int[][] edges1 = {{0,2}, {0,4}, {1,0}, {2,1}, {3,0}, {4,3}, {5,3}, {6,7}, {6,8}, {8,7}};
+        UndirectedGraph<String,Number> graph = new UndirectedSparseMultigraph<String,Number>();
+        createEdges(v, edges1, graph);
+        
+        List<Set<String>> c = new ArrayList<Set<String>>();
+        for (int i = 0; i < 4; i++)
+            c.add(i, new HashSet<String>());
+
+        c.get(0).add(v[0]);
+        c.get(0).add(v[1]);
+        c.get(0).add(v[2]);
+
+        c.get(1).add(v[0]);
+        c.get(1).add(v[3]);
+        c.get(1).add(v[4]);
+
+        c.get(2).add(v[5]);
+        c.get(2).add(v[3]);
+
+        c.get(3).add(v[6]);
+        c.get(3).add(v[7]);
+        c.get(3).add(v[8]);
+
+//        Set[] c = new Set[4];
+//        for (int i = 0; i < c.length; i++)
+//            c[i] = new HashSet();
+//        
+//        c[0].add(v[0]);
+//        c[0].add(v[1]);
+//        c[0].add(v[2]);
+//        
+//        c[1].add(v[0]);
+//        c[1].add(v[3]);
+//        c[1].add(v[4]);
+//        
+//        c[2].add(v[5]);
+//        c[2].add(v[3]);
+//        
+//        c[3].add(v[6]);
+//        c[3].add(v[7]);
+//        c[3].add(v[8]);
+
+        testComponents(graph, v, c);
+	}
+
+    public void testComponents(UndirectedGraph<String,Number> graph, String[] vertices, List<Set<String>> c)
+    {
+        BicomponentClusterer<String,Number> finder = new BicomponentClusterer<String,Number>();
+        Set<Set<String>> bicomponents = finder.apply(graph);
+        
+        // check number of components
+        assertEquals(bicomponents.size(), c.size());
+
+        // diagnostic; should be commented out for typical unit tests
+//        for (int i = 0; i < bicomponents.size(); i++)
+//        {
+//            System.out.print("Component " + i + ": ");
+//            Set bicomponent = bicomponents.getCluster(i);
+//            for (Iterator iter = bicomponent.iterator(); iter.hasNext(); )
+//            {
+//                Vertex w = (Vertex)iter.next();
+//                System.out.print(sl.getLabel(w) + " ");
+//            }
+//            System.out.println();
+//        }
+//        System.out.println();
+        
+        // make sure that each set in c[] is found in bicomponents
+        List<Set<String>> clusterList = new ArrayList<Set<String>>(bicomponents);
+        boolean found = false;
+        for (int i = 0; i < c.size(); i++)
+        {
+            for (int j = 0; j < bicomponents.size(); j++)
+                if (clusterList.get(j).equals(c.get(i)))
+                {
+                    found = true;
+                    break;
+                }
+            assertTrue(found);
+        }
+        
+        // make sure that each vertex is represented in >=1 element of bicomponents 
+        Set<String> collapsedSet = new HashSet<String>();
+        for(Set<String> set : bicomponents) {
+        	collapsedSet.addAll(set);
+        }
+        for (String v : graph.getVertices())
+        {
+        	assertTrue(collapsedSet.contains(v));
+//        	assertFalse(((LinkedHashSet)vset).get(v).isEmpty());
+        }
+    }
+    
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/cluster/TestEdgeBetweennessClusterer.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/cluster/TestEdgeBetweennessClusterer.java
new file mode 100644
index 0000000..bf74037
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/cluster/TestEdgeBetweennessClusterer.java
@@ -0,0 +1,83 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.cluster;
+
+import java.util.Collection;
+import java.util.Set;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+
+
+/**
+ * @author Scott White
+ */
+public class TestEdgeBetweennessClusterer extends TestCase {
+    public static Test suite() {
+        return new TestSuite(TestEdgeBetweennessClusterer.class);
+    }
+    Supplier<Graph<Integer,Number>> graphFactory;
+    Supplier<Integer> vertexFactory;
+    Supplier<Number> edgeFactory;
+
+    @Override
+    protected void setUp() {
+        graphFactory = new Supplier<Graph<Integer,Number>>() {
+    		public Graph<Integer,Number> get() {
+    			return new SparseMultigraph<Integer,Number>();
+    		}
+    	};
+    	vertexFactory = new Supplier<Integer>() {
+    		int n = 0;
+    		public Integer get() { return n++; }
+    	};
+    	edgeFactory = new Supplier<Number>() {
+    		int n = 0;
+    		public Number get() { return n++; }
+    	};
+
+    }
+
+    public void testRanker() {
+    	
+    	Graph<Number,Number> graph = new SparseMultigraph<Number,Number>();
+    	for(int i=0; i<10; i++) {
+    		graph.addVertex(i+1);
+    	}
+    	int j=0;
+    	graph.addEdge(j++,1,2);
+    	graph.addEdge(j++,1,3);
+    	graph.addEdge(j++,2,3);
+    	graph.addEdge(j++,5,6);
+    	graph.addEdge(j++,5,7);
+    	graph.addEdge(j++,6,7);
+    	graph.addEdge(j++,8,10);
+    	graph.addEdge(j++,7,8);
+    	graph.addEdge(j++,7,10);
+    	graph.addEdge(j++,3,4);
+    	graph.addEdge(j++,4,6);
+    	graph.addEdge(j++,4,8);
+
+        Assert.assertEquals(graph.getVertexCount(),10);
+        Assert.assertEquals(graph.getEdgeCount(),12);
+
+        EdgeBetweennessClusterer<Number, Number> clusterer = new EdgeBetweennessClusterer<Number, Number>(3);
+        Collection<Set<Number>> clusters = clusterer.apply(graph);
+        
+        Assert.assertEquals(clusters.size(),3);
+    }
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/cluster/WeakComponentClustererTest.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/cluster/WeakComponentClustererTest.java
new file mode 100644
index 0000000..52ef2c0
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/cluster/WeakComponentClustererTest.java
@@ -0,0 +1,19 @@
+package edu.uci.ics.jung.algorithms.cluster;
+
+import junit.framework.TestCase;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+
+public class WeakComponentClustererTest extends TestCase {
+	
+	Graph<String,Number> graph =  TestGraphs.getDemoGraph();
+	
+	public void testWeakComponent() {
+		WeakComponentClusterer<String,Number> clusterer = 
+			new WeakComponentClusterer<String,Number>();
+//		Set<Set<String>> clusterSet = 
+		clusterer.apply(graph);
+//		System.err.println("set is "+clusterSet);
+	}
+
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/filters/impl/TestKNeighborhoodFilter.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/filters/impl/TestKNeighborhoodFilter.java
new file mode 100644
index 0000000..445060a
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/filters/impl/TestKNeighborhoodFilter.java
@@ -0,0 +1,62 @@
+package edu.uci.ics.jung.algorithms.filters.impl;
+
+/**
+ * @author Tom Nelson
+ */
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import edu.uci.ics.jung.algorithms.filters.Filter;
+import edu.uci.ics.jung.algorithms.filters.KNeighborhoodFilter;
+import edu.uci.ics.jung.algorithms.filters.KNeighborhoodFilter.EdgeType;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Graph;
+
+
+public class TestKNeighborhoodFilter extends TestCase {
+	
+	DirectedGraph<Number,Number> graph;
+	
+	public static Test suite() {
+		return new TestSuite(TestKNeighborhoodFilter.class);
+	}
+
+	@Override
+	protected void setUp() {
+		graph = new DirectedSparseMultigraph<Number,Number>();
+		for(int i=0; i<7; i++) {
+			graph.addVertex(i);
+		}
+		int j=0;
+		graph.addEdge(j++, 0, 1);
+		graph.addEdge(j++, 0, 2);
+		graph.addEdge(j++, 2, 3);
+		graph.addEdge(j++, 2, 4);
+		graph.addEdge(j++, 3, 5);
+		graph.addEdge(j++, 5, 6);
+		graph.addEdge(j++, 5, 0);
+		graph.addEdge(j++, 3, 0);
+		graph.addEdge(j++, 6, 7);
+	}
+
+	public void testIn() {
+		Filter<Number,Number> filter = new KNeighborhoodFilter<Number,Number>(0, 2, EdgeType.IN);
+		Graph<Number,Number> result = filter.apply(graph);
+		assertEquals(result.getVertexCount(), 4);
+		assertEquals(result.getEdgeCount(), 5);
+	}
+	public void testOut() {
+		Filter<Number,Number> filter = new KNeighborhoodFilter<Number,Number>(0, 2, EdgeType.OUT);
+		Graph<Number,Number> result = filter.apply(graph);
+		assertEquals(result.getVertexCount(), 5);
+		assertEquals(result.getEdgeCount(), 5);
+	}
+	public void testInOut() {
+		Filter<Number,Number> filter = new KNeighborhoodFilter<Number,Number>(0, 2, EdgeType.IN_OUT);
+		Graph<Number,Number> result = filter.apply(graph);
+		assertEquals(result.getVertexCount(), 7);
+		assertEquals(result.getEdgeCount(), 8);
+	}
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/flows/TestEdmondsKarpMaxFlow.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/flows/TestEdmondsKarpMaxFlow.java
new file mode 100644
index 0000000..6993f1e
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/flows/TestEdmondsKarpMaxFlow.java
@@ -0,0 +1,211 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.flows;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+/**
+ * @author Scott White, Joshua O'Madadhain, Tom Nelson
+ */
+public class TestEdmondsKarpMaxFlow extends TestCase {
+
+	public static Test suite() {
+		return new TestSuite(TestEdmondsKarpMaxFlow.class);
+	}
+
+	@Override
+  protected void setUp() {
+
+	}
+
+    public void testSanityChecks() 
+    {
+        DirectedGraph<Number,Number> g = new DirectedSparseMultigraph<Number,Number>();
+        Number source = new Integer(1);
+        Number sink = new Integer(2);
+        g.addVertex(source);
+        g.addVertex(sink);
+        
+        Number v = new Integer(3);
+        
+        DirectedGraph<Number,Number> h = new DirectedSparseMultigraph<Number,Number>();
+        Number w = new Integer(4);
+        g.addVertex(w);
+        
+        try
+        {
+            new EdmondsKarpMaxFlow<Number,Number>(g, source, source, null, null, null);
+            fail("source and sink vertices not distinct");
+        }
+        catch (IllegalArgumentException iae) {}
+
+        try
+        {
+            new EdmondsKarpMaxFlow<Number,Number>(h, source, w, null, null, null);
+            fail("source and sink vertices not both part of specified graph");
+        }
+        catch (IllegalArgumentException iae) {}
+
+        try
+        {
+            new EdmondsKarpMaxFlow<Number,Number>(g, source, v, null, null, null);
+            fail("source and sink vertices not both part of specified graph");
+        }
+        catch (IllegalArgumentException iae) {}
+    }
+    
+	public void testSimpleFlow() {
+		DirectedGraph<Number,Number> graph = new DirectedSparseMultigraph<Number,Number>();
+		Supplier<Number> edgeFactory = new Supplier<Number>() {
+			int count = 0;
+			public Number get() {
+				return count++;
+			}
+		};
+
+		Map<Number,Number> edgeCapacityMap = new HashMap<Number,Number>();
+		for(int i=0; i<6; i++) {
+			graph.addVertex(i);
+		}
+		
+		Map<Number,Number> edgeFlowMap = new HashMap<Number,Number>();
+
+		graph.addEdge(edgeFactory.get(),0,1,EdgeType.DIRECTED);
+		edgeCapacityMap.put(0, 16);
+
+		graph.addEdge(edgeFactory.get(),0,2,EdgeType.DIRECTED);
+		edgeCapacityMap.put(1,13);
+
+		graph.addEdge(edgeFactory.get(),1,2,EdgeType.DIRECTED);
+		edgeCapacityMap.put(2, 6);
+
+		graph.addEdge(edgeFactory.get(),1,3,EdgeType.DIRECTED);
+		edgeCapacityMap.put(3, 12);
+
+		graph.addEdge(edgeFactory.get(),2,4,EdgeType.DIRECTED);
+		edgeCapacityMap.put(4, 14);
+
+		graph.addEdge(edgeFactory.get(),3,2,EdgeType.DIRECTED);
+		edgeCapacityMap.put(5, 9);
+
+		graph.addEdge(edgeFactory.get(),3,5,EdgeType.DIRECTED);
+		edgeCapacityMap.put(6, 20);
+
+		graph.addEdge(edgeFactory.get(),4,3,EdgeType.DIRECTED);
+		edgeCapacityMap.put(7, 7);
+
+		graph.addEdge(edgeFactory.get(),4,5,EdgeType.DIRECTED);
+		edgeCapacityMap.put(8, 4);
+
+		EdmondsKarpMaxFlow<Number,Number> ek =
+			new EdmondsKarpMaxFlow<Number,Number>(
+				graph,
+				0,
+				5,
+				Functions.<Number,Number>forMap(edgeCapacityMap, null),
+				edgeFlowMap,
+				edgeFactory);
+		ek.evaluate();
+
+		assertTrue(ek.getMaxFlow() == 23);
+        Set<Number> nodesInS = ek.getNodesInSourcePartition();
+        assertEquals(4,nodesInS.size());
+
+        for (Number v : nodesInS) {
+            Assert.assertTrue(v.intValue() != 3 && v.intValue() != 5);
+        }
+
+        Set<Number> nodesInT = ek.getNodesInSinkPartition();
+        assertEquals(2,nodesInT.size());
+
+        for (Number v : nodesInT) {
+            Assert.assertTrue(v.intValue() == 3 || v.intValue() == 5);
+        }
+
+        Set<Number> minCutEdges = ek.getMinCutEdges();
+        int maxFlow = 0;
+        for (Number e : minCutEdges) {
+            Number flow = edgeFlowMap.get(e);
+            maxFlow += flow.intValue();
+        }
+        Assert.assertEquals(23,maxFlow);
+        Assert.assertEquals(3,minCutEdges.size());
+	}
+
+	public void testAnotherSimpleFlow() {
+		DirectedGraph<Number,Number> graph = new DirectedSparseMultigraph<Number,Number>();
+		Supplier<Number> edgeFactory = new Supplier<Number>() {
+			int count=0;
+			public Number get() {
+				return count++;
+			}
+		};
+
+		Map<Number,Number> edgeCapacityMap = new HashMap<Number,Number>();
+		for(int i=0; i<6; i++) {
+			graph.addVertex(i);
+		}
+		
+		Map<Number,Number> edgeFlowMap = new HashMap<Number,Number>();
+
+		graph.addEdge(edgeFactory.get(),0,1,EdgeType.DIRECTED);
+		edgeCapacityMap.put(0,5);
+		
+		graph.addEdge(edgeFactory.get(),0,2,EdgeType.DIRECTED);
+		edgeCapacityMap.put(1,3);
+		
+		graph.addEdge(edgeFactory.get(),1,5,EdgeType.DIRECTED);
+		edgeCapacityMap.put(2,2);
+		
+		graph.addEdge(edgeFactory.get(),1,2,EdgeType.DIRECTED);
+		edgeCapacityMap.put(3,8);
+		
+		graph.addEdge(edgeFactory.get(),2,3,EdgeType.DIRECTED);
+		edgeCapacityMap.put(4,4);
+		
+		graph.addEdge(edgeFactory.get(),2,4,EdgeType.DIRECTED);
+		edgeCapacityMap.put(5,2);
+		
+		graph.addEdge(edgeFactory.get(),3,4,EdgeType.DIRECTED);
+		edgeCapacityMap.put(6,3);
+		
+		graph.addEdge(edgeFactory.get(),3,5,EdgeType.DIRECTED);
+		edgeCapacityMap.put(7,6);
+		
+		graph.addEdge(edgeFactory.get(),4,5,EdgeType.DIRECTED);
+		edgeCapacityMap.put(8,1);
+
+		EdmondsKarpMaxFlow<Number,Number> ek =
+			new EdmondsKarpMaxFlow<Number,Number>(
+				graph,
+				0,
+				5,
+				Functions.<Number,Number>forMap(edgeCapacityMap, null),
+				edgeFlowMap,
+				edgeFactory);
+		ek.evaluate();
+
+		assertTrue(ek.getMaxFlow() == 7);
+	}
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/TestLattice2D.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/TestLattice2D.java
new file mode 100644
index 0000000..fec9b9e
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/TestLattice2D.java
@@ -0,0 +1,87 @@
+package edu.uci.ics.jung.algorithms.generators;
+
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.UndirectedSparseMultigraph;
+
+
+public class TestLattice2D extends TestCase {
+	
+	protected Supplier<UndirectedGraph<String,Number>> undirectedGraphFactory;
+    protected Supplier<DirectedGraph<String,Number>> directedGraphFactory;
+	protected Supplier<String> vertexFactory;
+	protected Supplier<Number> edgeFactory;
+
+	@Override
+	protected void setUp() {
+		undirectedGraphFactory = new Supplier<UndirectedGraph<String,Number>>() {
+			public UndirectedGraph<String,Number> get() {
+				return new UndirectedSparseMultigraph<String,Number>();
+			}
+		};
+		directedGraphFactory = new Supplier<DirectedGraph<String,Number>>() {
+            public DirectedGraph<String,Number> get() {
+                return new DirectedSparseMultigraph<String,Number>();
+            }
+        };
+
+		vertexFactory = new Supplier<String>() {
+			int count;
+			public String get() {
+				return Character.toString((char)('A'+count++));
+			}
+		};
+		edgeFactory = 
+			new Supplier<Number>() {
+			int count;
+			public Number get() {
+				return count++;
+			}
+		};
+	}
+
+	public void testCreateSingular() 
+	{
+	    try
+	    {
+	        generate(1, 0, 0);
+	        fail("Did not reject lattice of size < 2");
+	    }
+	    catch (IllegalArgumentException iae) {}
+	}
+	
+	public void testget() {
+		for (int i = 3; i <= 10; i++) {
+		    for (int j = 0; j < 2; j++) {
+		        for (int k = 0; k < 2; k++) {
+        			Lattice2DGenerator<String,Number> generator = generate(i, j, k);
+    			    Graph<String,Number> graph = generator.get();
+                    Assert.assertEquals(i*i, graph.getVertexCount());
+                    checkEdgeCount(generator, graph);
+		        }
+		    }
+		}
+	}
+	
+	protected Lattice2DGenerator<String, Number> generate(int i, int j, int k)
+	{
+	    return new Lattice2DGenerator<String,Number>(
+                k == 0 ? undirectedGraphFactory : directedGraphFactory, 
+                vertexFactory, edgeFactory,
+                i, j == 0 ? true : false); // toroidal?
+	}
+	
+	protected void checkEdgeCount(Lattice2DGenerator<String, Number> generator,
+		Graph<String, Number> graph) 
+	{
+        Assert.assertEquals(generator.getGridEdgeCount(), graph.getEdgeCount());
+	}
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/TestBarabasiAlbert.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/TestBarabasiAlbert.java
new file mode 100644
index 0000000..ba50d3c
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/TestBarabasiAlbert.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2016, the JUNG Project and the Regents of the University 
+ * of California.  All rights reserved.
+ *
+ * This software is open-source under the BSD license; see
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.algorithms.generators.random;
+
+import java.util.HashSet;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.DirectedSparseGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseGraph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+import edu.uci.ics.jung.graph.UndirectedSparseGraph;
+import edu.uci.ics.jung.graph.UndirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * @author W. Giordano
+ * @author Scott White
+ * @author James Marchant
+ */
+public class TestBarabasiAlbert extends TestCase {
+	protected Supplier<Graph<Integer, Number>> graphFactory;
+	protected Supplier<Integer> vertexFactory;
+	protected Supplier<Number> edgeFactory;
+
+	protected int init_vertices = 1;
+	protected int edges_to_add_per_timestep = 1;
+	protected int random_seed = 0;
+	protected int num_timesteps = 10;
+	protected int num_tests = 10;
+
+	public static Test suite() {
+		return new TestSuite(TestBarabasiAlbert.class);
+	}
+
+	@Override
+	protected void setUp() {
+		graphFactory = new Supplier<Graph<Integer, Number>>() {
+			public Graph<Integer, Number> get() {
+				return new SparseMultigraph<Integer, Number>();
+			}
+		};
+		vertexFactory = new Supplier<Integer>() {
+			int count;
+
+			public Integer get() {
+				return count++;
+			}
+		};
+		edgeFactory = new Supplier<Number>() {
+			int count;
+
+			public Number get() {
+				return count++;
+			}
+		};
+	}
+
+	private Graph<Integer, Number> generateAndTestSizeOfBarabasiAlbertGraph(
+			Supplier<Graph<Integer, Number>> graphFactory, Supplier<Integer> vertexFactory,
+			Supplier<Number> edgeFactory, int init_vertices, int edges_to_add_per_timestep, int random_seed,
+			int num_tests) {
+		BarabasiAlbertGenerator<Integer, Number> generator = new BarabasiAlbertGenerator<Integer, Number>(graphFactory,
+				vertexFactory, edgeFactory, init_vertices, edges_to_add_per_timestep, random_seed,
+				new HashSet<Integer>());
+
+		Graph<Integer, Number> graph = null;
+		// test the graph size over {@code num_tests} intervals of {@code
+		// num_timesteps} timesteps
+		for (int i = 1; i <= num_tests; i++) {
+			generator.evolveGraph(num_timesteps);
+			graph = generator.get();
+			assertEquals(graph.getVertexCount(), (i * num_timesteps) + init_vertices);
+			assertEquals(graph.getEdgeCount(), edges_to_add_per_timestep * (i * num_timesteps));
+		}
+
+		return graph;
+	}
+
+	public void testMultigraphCreation() {
+		generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices,
+				edges_to_add_per_timestep, random_seed, num_tests);
+	}
+
+	public void testDirectedMultigraphCreation() {
+		graphFactory = new Supplier<Graph<Integer, Number>>() {
+			public Graph<Integer, Number> get() {
+				return new DirectedSparseMultigraph<Integer, Number>();
+			}
+		};
+
+		generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices,
+				edges_to_add_per_timestep, random_seed, num_tests);
+	}
+
+	public void testUndirectedMultigraphCreation() {
+		graphFactory = new Supplier<Graph<Integer, Number>>() {
+			public Graph<Integer, Number> get() {
+				return new UndirectedSparseMultigraph<Integer, Number>();
+			}
+		};
+
+		generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices,
+				edges_to_add_per_timestep, random_seed, num_tests);
+	}
+
+	public void testGraphCreation() {
+		graphFactory = new Supplier<Graph<Integer, Number>>() {
+			public Graph<Integer, Number> get() {
+				return new SparseGraph<Integer, Number>();
+			}
+		};
+
+		generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices,
+				edges_to_add_per_timestep, random_seed, num_tests);
+	}
+
+	public void testDirectedGraphCreation() {
+		graphFactory = new Supplier<Graph<Integer, Number>>() {
+			public Graph<Integer, Number> get() {
+				return new DirectedSparseGraph<Integer, Number>();
+			}
+		};
+
+		generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices,
+				edges_to_add_per_timestep, random_seed, num_tests);
+	}
+
+	public void testUndirectedGraphCreation() {
+		graphFactory = new Supplier<Graph<Integer, Number>>() {
+			public Graph<Integer, Number> get() {
+				return new UndirectedSparseGraph<Integer, Number>();
+			}
+		};
+
+		generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices,
+				edges_to_add_per_timestep, random_seed, num_tests);
+	}
+
+	/**
+	 * Due to the way the Barabasi-Albert algorithm works there should be no
+	 * opportunities for the generation of self-loops within the graph.
+	 */
+	public void testNoSelfLoops() {
+		graphFactory = new Supplier<Graph<Integer, Number>>() {
+			public Graph<Integer, Number> get() {
+				return new UndirectedSparseGraph<Integer, Number>() {
+					private static final long serialVersionUID = 1L;
+
+					/**
+					 * This anonymous class works as an UndirectedSparseGraph
+					 * but will not accept edges that connect a vertex to
+					 * itself.
+					 */
+					@Override
+					public boolean addEdge(Number edge, Pair<? extends Integer> endpoints, EdgeType edgeType) {
+						if (endpoints == null)
+							throw new IllegalArgumentException("endpoints may not be null");
+
+						Integer v1 = endpoints.getFirst();
+						Integer v2 = endpoints.getSecond();
+
+						if (v1.equals(v2))
+							throw new IllegalArgumentException("No self-loops");
+						else
+							return super.addEdge(edge, endpoints, edgeType);
+					}
+				};
+			}
+		};
+
+		generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices,
+				edges_to_add_per_timestep, random_seed, num_tests);
+	}
+
+	public void testPreconditions() {
+		// test init_vertices = 0
+		try {
+			generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, 0,
+					edges_to_add_per_timestep, random_seed, num_tests);
+			fail();
+		} catch (IllegalArgumentException e) {
+		}
+
+		// test negative init_vertices
+		try {
+			generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, -1,
+					edges_to_add_per_timestep, random_seed, num_tests);
+			fail();
+		} catch (IllegalArgumentException e) {
+		}
+
+		// test edges_to_add_per_timestep = 0
+		try {
+			generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices, 0,
+					random_seed, num_tests);
+			fail();
+		} catch (IllegalArgumentException e) {
+		}
+
+		// test negative edges_to_add_per_timestep
+		try {
+			generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, init_vertices, -1,
+					random_seed, num_tests);
+			fail();
+		} catch (IllegalArgumentException e) {
+		}
+
+		// test edges_to_add_per_timestep > init_vertices
+		try {
+			generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory, edgeFactory, 2, 3, random_seed,
+					num_tests);
+			fail();
+		} catch (IllegalArgumentException e) {
+		}
+	}
+
+	/**
+	 * Every node should have an out-degree AT LEAST equal to the number of
+	 * edges added per timestep (dependent on if it is directed or undirected).
+	 */
+	public void testEveryNodeHasCorrectMinimumNumberOfEdges() {
+		Graph<Integer, Number> graph = generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory,
+				edgeFactory, init_vertices, edges_to_add_per_timestep, random_seed, num_tests);
+
+		for (Integer v : graph.getVertices()) {
+			assertTrue(graph.outDegree(v) >= edges_to_add_per_timestep);
+		}
+	}
+
+	/**
+	 * Check that not every edge goes to one node; the in-degree of any node
+	 * should be strictly less than the number of edges.
+	 */
+	public void testNotEveryEdgeToOneNode() {
+		Graph<Integer, Number> graph = generateAndTestSizeOfBarabasiAlbertGraph(graphFactory, vertexFactory,
+				edgeFactory, init_vertices, edges_to_add_per_timestep, random_seed, num_tests);
+
+		for (Integer v : graph.getVertices()) {
+			assertTrue(graph.inDegree(v) < graph.getEdgeCount());
+		}
+	}
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/TestEppsteinPowerLawGenerator.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/TestEppsteinPowerLawGenerator.java
new file mode 100644
index 0000000..ba72951
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/TestEppsteinPowerLawGenerator.java
@@ -0,0 +1,106 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.generators.random;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+
+/**
+ * @author Scott White
+ */
+public class TestEppsteinPowerLawGenerator extends TestCase {
+	
+	Supplier<Graph<Integer,Number>> graphFactory;
+	Supplier<Integer> vertexFactory;
+	Supplier<Number> edgeFactory;
+
+	public static Test suite() {
+		return new TestSuite(TestEppsteinPowerLawGenerator.class);
+	}
+
+	@Override
+  protected void setUp() {
+		graphFactory = new Supplier<Graph<Integer,Number>>() {
+			public Graph<Integer,Number> get() {
+				return new SparseMultigraph<Integer,Number>();
+			}
+		};
+		vertexFactory = new Supplier<Integer>() {
+			int count;
+			public Integer get() {
+				return count++;
+			}
+		};
+		edgeFactory = 
+			new Supplier<Number>() {
+			int count;
+			public Number get() {
+				return count++;
+			}
+		};
+	}
+
+    public void testSimpleDirectedCase() {
+
+        for (int r=0; r<10; r++) {
+            EppsteinPowerLawGenerator<Integer, Number> generator = 
+            	new EppsteinPowerLawGenerator<Integer, Number>(graphFactory, vertexFactory, edgeFactory, 10,40,r);
+            generator.setSeed(2);
+
+            Graph<Integer, Number> graph = generator.get();
+            Assert.assertEquals(graph.getVertexCount(),10);
+            Assert.assertEquals(graph.getEdgeCount(),40);
+        }
+
+    }
+
+    // TODO: convert what is needed for this test
+//    public void testPowerLawProperties() {
+//
+//        //long start = System.currentTimeMillis();
+//        EppsteinPowerLawGenerator generator = new EppsteinPowerLawGenerator(vertexFactory, edgeFactory,
+//        		500,1500,100000);
+//        generator.setSeed(5);
+//        Graph graph = (Graph) generator.generateGraph();
+//        //long stop = System.currentTimeMillis();
+//        //System.out.println((stop-start)/1000l);
+//
+//        DoubleArrayList degreeList = DegreeDistributions.getOutdegreeValues(graph.getVertices());
+//        int maxDegree = (int) Descriptive.max(degreeList);
+//        Histogram degreeHistogram = GraphStatistics.createHistogram(degreeList,0,maxDegree,1);
+//        //for (int index=0;index<maxDegree;index++) {
+//        //    System.out.println(degreeHistogram.binIndex(index) + " " + degreeHistogram.binHeight(index));
+//        //}
+//        //if it's power law, 0 is going to have the highest bin count
+//        Assert.assertTrue(degreeHistogram.binHeight(0) + degreeHistogram.binHeight(1) > degreeHistogram.binHeight(2) + degreeHistogram.binHeight(3));
+//
+//        generator = new EppsteinPowerLawGenerator(500,1500,0);
+//        graph = (Graph) generator.generateGraph();
+//        degreeList = DegreeDistributions.getOutdegreeValues(graph.getVertices());
+//        maxDegree = (int) Descriptive.max(degreeList);
+//        degreeHistogram = GraphStatistics.createHistogram(degreeList,0,maxDegree,1);
+//        //for (int index=0;index<maxDegree;index++) {
+//        //    System.out.println(degreeHistogram.binIndex(index) + " " + degreeHistogram.binHeight(index));
+//        //}
+//        //if it's not power law, 0 is not going to have the highest bin count rather it will start to go up
+//        Assert.assertTrue(degreeHistogram.binHeight(0) < degreeHistogram.binHeight(1));
+//
+//
+//
+//    }
+
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/TestErdosRenyi.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/TestErdosRenyi.java
new file mode 100644
index 0000000..5190bef
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/TestErdosRenyi.java
@@ -0,0 +1,71 @@
+package edu.uci.ics.jung.algorithms.generators.random;
+
+/**
+ * @author W. Giordano, Scott White
+ */
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.UndirectedSparseMultigraph;
+
+
+public class TestErdosRenyi extends TestCase {
+	
+	Supplier<UndirectedGraph<String,Number>> graphFactory;
+	Supplier<String> vertexFactory;
+	Supplier<Number> edgeFactory;
+
+	public static Test suite() {
+		return new TestSuite(TestErdosRenyi.class);
+	}
+
+	@Override
+  protected void setUp() {
+		graphFactory = new Supplier<UndirectedGraph<String,Number>>() {
+			public UndirectedGraph<String,Number> get() {
+				return new UndirectedSparseMultigraph<String,Number>();
+			}
+		};
+		vertexFactory = new Supplier<String>() {
+			int count;
+			public String get() {
+				return Character.toString((char)('A'+count++));
+			}
+		};
+		edgeFactory = 
+			new Supplier<Number>() {
+			int count;
+			public Number get() {
+				return count++;
+			}
+		};
+	}
+
+	public void test() {
+
+        int numVertices = 100;
+        int total = 0;
+		for (int i = 1; i <= 10; i++) {
+			ErdosRenyiGenerator<String,Number> generator = 
+				new ErdosRenyiGenerator<String,Number>(graphFactory, vertexFactory, edgeFactory,
+					numVertices,0.1);
+            generator.setSeed(0);
+
+			Graph<String,Number> graph = generator.get();
+			Assert.assertTrue(graph.getVertexCount() == numVertices);
+            total += graph.getEdgeCount();
+		}
+        total /= 10.0;
+        Assert.assertTrue(total > 495-50 && total < 495+50);
+
+	}
+	  
+  
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/TestKleinberg.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/TestKleinberg.java
new file mode 100644
index 0000000..b1e0c55
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/generators/random/TestKleinberg.java
@@ -0,0 +1,39 @@
+package edu.uci.ics.jung.algorithms.generators.random;
+
+
+import junit.framework.Assert;
+import edu.uci.ics.jung.algorithms.generators.Lattice2DGenerator;
+import edu.uci.ics.jung.algorithms.generators.TestLattice2D;
+import edu.uci.ics.jung.graph.Graph;
+
+
+/**
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class TestKleinberg extends TestLattice2D {
+	
+    @Override
+    protected Lattice2DGenerator<String, Number> generate(int i, int j, int k)
+    {
+        return new KleinbergSmallWorldGenerator<String,Number>(
+                k == 0 ? undirectedGraphFactory : directedGraphFactory, 
+                vertexFactory, edgeFactory,
+                i, // rows
+                i, // columns
+                0.1, // clustering exponent
+                j == 0 ? true : false); // toroidal?
+    }
+    
+    @Override
+    protected void checkEdgeCount(Lattice2DGenerator<String, Number> generator,
+    	Graph<String, Number> graph) 
+    
+    {
+        Assert.assertEquals(
+        	generator.getGridEdgeCount() +
+                ((KleinbergSmallWorldGenerator<?, ?>)generator).getConnectionCount()
+                	* graph.getVertexCount(), 
+            graph.getEdgeCount());
+    }
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/importance/TestBetweennessCentrality.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/importance/TestBetweennessCentrality.java
new file mode 100644
index 0000000..87a6a37
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/importance/TestBetweennessCentrality.java
@@ -0,0 +1,119 @@
+/*
+* Copyright (c) 2003, The JUNG Authors
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.importance;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseGraph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.UndirectedSparseGraph;
+
+/**
+ * @author Scott White
+ */
+public class TestBetweennessCentrality extends TestCase {
+    public static Test suite() {
+        return new TestSuite(TestBetweennessCentrality.class);
+    }
+
+    @Override
+    protected void setUp() {}
+
+//    private static <V,E> E getEdge(Graph<V,E> g, int v1Index, int v2Index, BidiMap<V,Integer> id) {
+//        V v1 = id.getKey(v1Index);
+//        V v2 = id.getKey(v2Index);
+//        return g.findEdge(v1, v2);
+//    }
+
+    public void testRanker() {
+        UndirectedGraph<Integer,Integer> graph = 
+        	new UndirectedSparseGraph<Integer,Integer>();
+        for(int i=0; i<9; i++) {
+        	graph.addVertex(i);
+        }
+
+		int edge = 0;
+		graph.addEdge(edge++, 0,1);
+		graph.addEdge(edge++, 0,6);
+		graph.addEdge(edge++, 1,2);
+		graph.addEdge(edge++, 1,3);
+		graph.addEdge(edge++, 2,4);
+		graph.addEdge(edge++, 3,4);
+		graph.addEdge(edge++, 4,5);
+		graph.addEdge(edge++, 5,8);
+		graph.addEdge(edge++, 7,8);
+		graph.addEdge(edge++, 6,7);
+
+        BetweennessCentrality<Integer,Integer> bc = 
+        	new BetweennessCentrality<Integer,Integer>(graph);
+        bc.setRemoveRankScoresOnFinalize(false);
+        bc.evaluate();
+
+//        System.out.println("ranking");
+//        for (int i = 0; i < 9; i++) 
+//        	System.out.println(String.format("%d: %f", i, bc.getVertexRankScore(i)));
+        
+        Assert.assertEquals(bc.getVertexRankScore(0)/28.0,0.2142,.001);
+        Assert.assertEquals(bc.getVertexRankScore(1)/28.0,0.2797,.001);
+        Assert.assertEquals(bc.getVertexRankScore(2)/28.0,0.0892,.001);
+        Assert.assertEquals(bc.getVertexRankScore(3)/28.0,0.0892,.001);
+        Assert.assertEquals(bc.getVertexRankScore(4)/28.0,0.2797,.001);
+        Assert.assertEquals(bc.getVertexRankScore(5)/28.0,0.2142,.001);
+        Assert.assertEquals(bc.getVertexRankScore(6)/28.0,0.1666,.001);
+        Assert.assertEquals(bc.getVertexRankScore(7)/28.0,0.1428,.001);
+        Assert.assertEquals(bc.getVertexRankScore(8)/28.0,0.1666,.001);
+
+        Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(0,1)),
+        		10.66666,.001);
+
+        Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(0,1)),10.66666,.001);
+        Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(0,6)),9.33333,.001);
+        Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(1,2)),6.5,.001);
+        Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(1,3)),6.5,.001);
+        Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(2,4)),6.5,.001);
+        Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(3,4)),6.5,.001);
+        Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(4,5)),10.66666,.001);
+        Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(5,8)),9.33333,.001);
+        Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(6,7)),8.0,.001);
+        Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(7,8)),8.0,.001);
+    }
+    
+    public void testRankerDirected() {
+    	DirectedGraph<Integer,Integer> graph = new DirectedSparseGraph<Integer,Integer>();
+    	for(int i=0; i<5; i++) {
+    		graph.addVertex(i);
+    	}
+
+    	int edge=0;
+    	graph.addEdge(edge++, 0,1);
+    	graph.addEdge(edge++, 1,2);
+    	graph.addEdge(edge++, 3,1);
+    	graph.addEdge(edge++, 4,2);
+
+    	BetweennessCentrality<Integer,Integer> bc = 
+    		new BetweennessCentrality<Integer,Integer>(graph);
+    	bc.setRemoveRankScoresOnFinalize(false);
+    	bc.evaluate();
+
+    	Assert.assertEquals(bc.getVertexRankScore(0),0,.001);
+    	Assert.assertEquals(bc.getVertexRankScore(1),2,.001);
+    	Assert.assertEquals(bc.getVertexRankScore(2),0,.001);
+    	Assert.assertEquals(bc.getVertexRankScore(3),0,.001);
+    	Assert.assertEquals(bc.getVertexRankScore(4),0,.001);
+
+    	Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(0,1)),2,.001);
+    	Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(1,2)),3,.001);
+    	Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(3,1)),2,.001);
+    	Assert.assertEquals(bc.getEdgeRankScore(graph.findEdge(4,2)),1,.001);
+    }
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/importance/TestKStepMarkov.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/importance/TestKStepMarkov.java
new file mode 100644
index 0000000..9ff58be
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/importance/TestKStepMarkov.java
@@ -0,0 +1,86 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.importance;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+
+
+/**
+ * @author Scott White
+ * @author Tom Nelson - adapted to jung2
+ */
+public class TestKStepMarkov extends TestCase {
+    public final static String EDGE_WEIGHT = "edu.uci.ics.jung.edge_weight";
+	DirectedGraph<Number,Number> mGraph;
+    double[][] mTransitionMatrix;
+    Map<Number,Number> edgeWeights = new HashMap<Number,Number>();
+
+    public static Test suite() {
+        return new TestSuite(TestKStepMarkov.class);
+    }
+
+    @Override
+    protected void setUp()
+    {
+        mGraph = new DirectedSparseMultigraph<Number,Number>();
+        mTransitionMatrix = new double[][]
+           {{0.0, 0.5, 0.5},
+            {1.0/3.0, 0.0, 2.0/3.0},
+            {1.0/3.0, 2.0/3.0, 0.0}};
+
+        for (int i = 0; i < mTransitionMatrix.length; i++)
+        	mGraph.addVertex(i);
+
+        for (int i = 0; i < mTransitionMatrix.length; i++) {
+            for (int j = 0; j < mTransitionMatrix[i].length; j++)
+            {
+                if (mTransitionMatrix[i][j] > 0)
+                {
+                	int edge = i*mTransitionMatrix.length+j;
+                	mGraph.addEdge(edge, i, j);
+                	edgeWeights.put(edge, mTransitionMatrix[i][j]);
+                }
+            }
+        }
+    }
+
+    public void testRanker() {
+
+        Set<Number> priors = new HashSet<Number>();
+        priors.add(1);
+        priors.add(2);
+        KStepMarkov<Number,Number> ranker = new KStepMarkov<Number,Number>(mGraph,priors,2,edgeWeights);
+//        ranker.evaluate();
+//        System.out.println(ranker.getIterations());
+
+        for (int i = 0; i < 10; i++) 
+        {
+//            System.out.println(ranker.getIterations());
+//	        for (Number n : mGraph.getVertices())
+//	        	System.out.println(n + ": " + ranker.getVertexRankScore(n));
+            
+	        ranker.step();
+        }
+        
+        
+        List<Ranking<?>> rankings = ranker.getRankings();
+//        System.out.println(rankings);
+    }
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/importance/TestWeightedNIPaths.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/importance/TestWeightedNIPaths.java
new file mode 100644
index 0000000..59c0805
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/importance/TestWeightedNIPaths.java
@@ -0,0 +1,85 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.importance;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+
+/**
+ * @author Scott White, adapted to jung2 by Tom Nelson
+ */
+public class TestWeightedNIPaths extends TestCase {
+	
+	Supplier<String> vertexFactory;
+	Supplier<Number> edgeFactory;
+
+    public static Test suite() {
+        return new TestSuite(TestWeightedNIPaths.class);
+    }
+
+    @Override
+    protected void setUp() {
+    	vertexFactory = new Supplier<String>() {
+    		char a = 'A';
+			public String get() {
+				return Character.toString(a++);
+			}};
+    	edgeFactory = new Supplier<Number>() {
+    		int count;
+			public Number get() {
+				return count++;
+			}};
+    }
+
+    public void testRanker() {
+
+        DirectedGraph<String,Number> graph = new DirectedSparseMultigraph<String,Number>();
+        for(int i=0; i<5; i++) {
+        	graph.addVertex(vertexFactory.get());
+        }
+
+        graph.addEdge(edgeFactory.get(), "A", "B");
+        graph.addEdge(edgeFactory.get(), "A", "C");
+        graph.addEdge(edgeFactory.get(), "A", "D");
+        graph.addEdge(edgeFactory.get(), "B", "A");
+        graph.addEdge(edgeFactory.get(), "B", "E");
+        graph.addEdge(edgeFactory.get(), "B", "D");
+        graph.addEdge(edgeFactory.get(), "C", "A");
+        graph.addEdge(edgeFactory.get(), "C", "E");
+        graph.addEdge(edgeFactory.get(), "C", "D");
+        graph.addEdge(edgeFactory.get(), "D", "A");
+        graph.addEdge(edgeFactory.get(), "D", "B");
+        graph.addEdge(edgeFactory.get(), "D", "C");
+        graph.addEdge(edgeFactory.get(), "D", "E");
+        
+        Set<String> priors = new HashSet<String>();
+        priors.add("A");
+
+        WeightedNIPaths<String,Number> ranker = 
+        	new WeightedNIPaths<String,Number>(graph, vertexFactory, edgeFactory, 2.0,3,priors);
+        ranker.evaluate();
+
+        Assert.assertEquals(ranker.getRankings().get(0).rankScore,0.277787,.0001);
+        Assert.assertEquals(ranker.getRankings().get(1).rankScore,0.222222,.0001);
+        Assert.assertEquals(ranker.getRankings().get(2).rankScore,0.166676,.0001);
+        Assert.assertEquals(ranker.getRankings().get(3).rankScore,0.166676,.0001);
+        Assert.assertEquals(ranker.getRankings().get(4).rankScore,0.166676,.0001);
+    }
+}
\ No newline at end of file
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/layout/FRLayout2Test.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/layout/FRLayout2Test.java
new file mode 100644
index 0000000..71822ac
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/layout/FRLayout2Test.java
@@ -0,0 +1,31 @@
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Dimension;
+import java.util.HashSet;
+import java.util.Set;
+
+import junit.framework.TestCase;
+import edu.uci.ics.jung.algorithms.layout.util.Relaxer;
+import edu.uci.ics.jung.algorithms.layout.util.VisRunner;
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+
+public class FRLayout2Test extends TestCase {
+	
+    protected Set<Integer> seedVertices = new HashSet<Integer>();
+
+	public void testFRLayout() {
+		
+		Graph<String,Number> graph = TestGraphs.getOneComponentGraph();
+
+		Layout<String,Number> layout = new FRLayout2<String,Number>(graph);
+		layout.setSize(new Dimension(600,600));
+		if(layout instanceof IterativeContext) {
+			layout.initialize();
+			Relaxer relaxer = new VisRunner((IterativeContext)layout);
+			relaxer.prerelax();
+			relaxer.relax();
+		}
+	}
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/layout/FRLayoutTest.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/layout/FRLayoutTest.java
new file mode 100644
index 0000000..9af60c1
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/layout/FRLayoutTest.java
@@ -0,0 +1,31 @@
+package edu.uci.ics.jung.algorithms.layout;
+
+import java.awt.Dimension;
+import java.util.HashSet;
+import java.util.Set;
+
+import junit.framework.TestCase;
+import edu.uci.ics.jung.algorithms.layout.util.Relaxer;
+import edu.uci.ics.jung.algorithms.layout.util.VisRunner;
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+
+public class FRLayoutTest extends TestCase {
+	
+    protected Set<Integer> seedVertices = new HashSet<Integer>();
+
+	public void testFRLayout() {
+		
+		Graph<String,Number> graph = TestGraphs.getOneComponentGraph();
+
+		Layout<String,Number> layout = new FRLayout<String,Number>(graph);
+		layout.setSize(new Dimension(600,600));
+		if(layout instanceof IterativeContext) {
+			layout.initialize();
+			Relaxer relaxer = new VisRunner((IterativeContext)layout);
+			relaxer.prerelax();
+			relaxer.relax();
+		}
+	}
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/metrics/TestTriad.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/metrics/TestTriad.java
new file mode 100644
index 0000000..f221e8c
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/metrics/TestTriad.java
@@ -0,0 +1,166 @@
+package edu.uci.ics.jung.algorithms.metrics;
+
+import junit.framework.TestCase;
+import edu.uci.ics.jung.algorithms.metrics.TriadicCensus;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+
+public class TestTriad extends TestCase {
+
+	public void testConfigurationFromPaper() {
+		DirectedGraph<Character,Number> g = new DirectedSparseMultigraph<Character,Number>();
+		char u = 'u';
+		g.addVertex(u);
+		char v = 'v';
+		g.addVertex(v);
+		char w = 'w';
+		g.addVertex(w);
+		g.addEdge(0, w, u);
+		g.addEdge(1, u, v);
+		g.addEdge(2, v, u);
+
+		assertEquals(35, TriadicCensus.<Character,Number>triCode(g, u, v, w));
+		assertEquals(7, TriadicCensus.triType(35));
+		assertEquals("111D", TriadicCensus.TRIAD_NAMES[7]);
+
+		assertEquals(7, TriadicCensus.triType(TriadicCensus.<Character,Number>triCode(g, u, w, v)));
+		assertEquals(7, TriadicCensus.triType(TriadicCensus.<Character,Number>triCode(g, v, u, w)));
+
+        long[] counts = TriadicCensus.getCounts(g);
+
+		for (int i = 1; i <= 16; i++) {
+			if (i == 7) {
+                assertEquals(1, counts[i]);
+			} else {
+                assertEquals(0, counts[i]);
+			}
+		}
+	}
+
+	public void testFourVertexGraph() {
+		// we'll set up a graph of
+		// t->u
+		// u->v
+		// and that's it.
+		// total count:
+		// 2: 1(t, u, w)(u, v, w)
+		// 6: 1(t, u, v)
+		// 1: 1(u, v, w)
+		DirectedGraph<Character,Number> g = new DirectedSparseMultigraph<Character,Number>();
+		char u = 'u';
+		g.addVertex(u);
+		char v = 'v';
+		g.addVertex(v);
+		char w = 'w';
+		g.addVertex(w);
+		char t = 't';
+		g.addVertex(t);
+		
+		g.addEdge(0, t, u );
+		g.addEdge(1, u, v );
+				
+        long[] counts = TriadicCensus.getCounts(g);
+		for (int i = 1; i <= 16; i++) {
+			if( i == 2 ) {
+                assertEquals("On " + i, 2, counts[i]);              
+			} else if (i == 6 || i == 1  ) {
+                assertEquals("On " + i, 1, counts[i]);
+			} else {
+                assertEquals(0, counts[i]);
+			}
+		}
+		
+		// now let's tweak to 
+		// t->u, u->v, v->t
+		// w->u, v->w
+		g.addEdge(2, v, t );
+		g.addEdge(3, w, u );
+		g.addEdge(4, v, w );
+
+		// that's two 030Cs. it's a 021D (v-t, v-w) and an 021U (t-u, w-u)
+        counts = TriadicCensus.getCounts(g);
+
+		for (int i = 1; i <= 16; i++) {
+			if( i == 10 /* 030C */ ) {
+                assertEquals("On " + i, 2, counts[i]);              
+			} else if (i == 4 || i == 5  ) {
+                assertEquals("On " + i, 1, counts[i]);
+			} else {
+                assertEquals("On " + i , 0, counts[i]);
+			}
+		}
+	}
+	
+	public void testThreeDotsThreeDashes() {
+		DirectedGraph<Character,Number> g = new DirectedSparseMultigraph<Character,Number>();
+		char u = 'u';
+		g.addVertex(u);
+		char v = 'v';
+		g.addVertex(v);
+		char w = 'w';
+		g.addVertex(w);
+
+        long[] counts = TriadicCensus.getCounts(g);
+
+		for (int i = 1; i <= 16; i++) {
+			if (i == 1) {
+                assertEquals(1, counts[i]);
+			} else {
+                assertEquals(0, counts[i]);
+			}
+		}
+
+		g.addEdge(0, v, u);
+		g.addEdge(1, u, v);
+		g.addEdge(2, v, w);
+		g.addEdge(3, w, v);
+		g.addEdge(4, u, w);
+		g.addEdge(5, w, u);
+
+        counts = TriadicCensus.getCounts(g);
+
+		for (int i = 1; i <= 16; i++) {
+			if (i == 16) {
+                assertEquals(1, counts[i]);
+			} else {
+                assertEquals("Count on " + i + " failed", 0, counts[i]);
+			}
+		}
+	}
+
+	/** **************Boring accounting for zero graphs*********** */
+	public void testNull() {
+		DirectedGraph<Character,Number> g = new DirectedSparseMultigraph<Character,Number>();
+        long[] counts = TriadicCensus.getCounts(g);
+
+		// t looks like a hashtable for the twelve keys
+		for (int i = 1; i < TriadicCensus.MAX_TRIADS; i++) {
+            assertEquals("Empty Graph doesn't have count 0", 0, counts[i]);
+		}
+	}
+
+	public void testOneVertex() {
+		DirectedGraph<Character,Number> g = new DirectedSparseMultigraph<Character,Number>();
+		g.addVertex('u');
+        long[] counts = TriadicCensus.getCounts(g);
+
+		// t looks like a hashtable for the twelve keys
+		for (int i = 1; i < TriadicCensus.MAX_TRIADS; i++) {
+            assertEquals("One vertex Graph doesn't have count 0", 0, counts[i]);
+		}
+	}
+
+	public void testTwoVertices() {
+		DirectedGraph<Character,Number> g = new DirectedSparseMultigraph<Character,Number>();
+		char v1, v2;
+		g.addVertex(v1 = 'u');
+		g.addVertex(v2 = 'v');
+		g.addEdge(0, v1, v2);
+        long[] counts = TriadicCensus.getCounts(g);
+
+		// t looks like a hashtable for the twelve keys
+		for (int i = 1; i < TriadicCensus.MAX_TRIADS; i++) {
+            assertEquals("Two vertex Graph doesn't have count 0", 0, counts[i]);
+		}
+	}
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestBetweennessCentrality.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestBetweennessCentrality.java
new file mode 100644
index 0000000..eda3f08
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestBetweennessCentrality.java
@@ -0,0 +1,138 @@
+/**
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Sep 17, 2008
+ * 
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import junit.framework.TestCase;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.DirectedSparseGraph;
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ *
+ */
+public class TestBetweennessCentrality extends TestCase 
+{
+//    public void testUndirected() {
+//        UndirectedGraph<Integer,Integer> graph = 
+//        	new UndirectedSparseGraph<Integer,Integer>();
+//        for(int i=0; i<9; i++) {
+//        	graph.addVertex(i);
+//        }
+//
+//		int edge = 0;
+//		graph.addEdge(edge++, 0,1);
+//		graph.addEdge(edge++, 0,6);
+//		graph.addEdge(edge++, 1,2);
+//		graph.addEdge(edge++, 1,3);
+//		graph.addEdge(edge++, 2,4);
+//		graph.addEdge(edge++, 3,4);
+//		graph.addEdge(edge++, 4,5);
+//		graph.addEdge(edge++, 5,8);
+//		graph.addEdge(edge++, 7,8);
+//		graph.addEdge(edge++, 6,7);
+//
+//        BetweennessCentrality<Integer,Integer> bc = 
+//        	new BetweennessCentrality<Integer,Integer>(graph);
+//        
+////        System.out.println("scoring");
+////        for (int i = 0; i < graph.getVertexCount(); i++) 
+////        	  System.out.println(String.format("%d: %f", i, bc.getVertexScore(i)));
+//
+//        Assert.assertEquals(bc.getVertexScore(0),6.000,.001);
+//        Assert.assertEquals(bc.getVertexScore(1),7.833,.001);
+//        Assert.assertEquals(bc.getVertexScore(2),2.500,.001);
+//        Assert.assertEquals(bc.getVertexScore(3),2.500,.001);
+//        Assert.assertEquals(bc.getVertexScore(4),7.833,.001);
+//        Assert.assertEquals(bc.getVertexScore(5),6.000,.001);
+//        Assert.assertEquals(bc.getVertexScore(6),4.666,.001);
+//        Assert.assertEquals(bc.getVertexScore(7),4.000,.001);
+//        Assert.assertEquals(bc.getVertexScore(8),4.666,.001);
+//
+//        Assert.assertEquals(bc.getEdgeScore(graph.findEdge(0,1)),10.666,.001);
+//        Assert.assertEquals(bc.getEdgeScore(graph.findEdge(0,6)),9.333,.001);
+//        Assert.assertEquals(bc.getEdgeScore(graph.findEdge(1,2)),6.500,.001);
+//        Assert.assertEquals(bc.getEdgeScore(graph.findEdge(1,3)),6.500,.001);
+//        Assert.assertEquals(bc.getEdgeScore(graph.findEdge(2,4)),6.500,.001);
+//        Assert.assertEquals(bc.getEdgeScore(graph.findEdge(3,4)),6.500,.001);
+//        Assert.assertEquals(bc.getEdgeScore(graph.findEdge(4,5)),10.666,.001);
+//        Assert.assertEquals(bc.getEdgeScore(graph.findEdge(5,8)),9.333,.001);
+//        Assert.assertEquals(bc.getEdgeScore(graph.findEdge(6,7)),8.000,.001);
+//        Assert.assertEquals(bc.getEdgeScore(graph.findEdge(7,8)),8.000,.001);
+//    }
+//    
+//    public void testDirected() 
+//    {
+//    	DirectedGraph<Integer,Integer> graph = new DirectedSparseGraph<Integer,Integer>();
+//    	for(int i=0; i<5; i++) 
+//    		graph.addVertex(i);
+//
+//    	int edge=0;
+//    	graph.addEdge(edge++, 0,1);
+//    	graph.addEdge(edge++, 1,2);
+//    	graph.addEdge(edge++, 3,1);
+//    	graph.addEdge(edge++, 4,2);
+//
+//    	BetweennessCentrality<Integer,Integer> bc = 
+//    		new BetweennessCentrality<Integer,Integer>(graph);
+//
+//    	Assert.assertEquals(bc.getVertexScore(0),0,.001);
+//    	Assert.assertEquals(bc.getVertexScore(1),2,.001);
+//    	Assert.assertEquals(bc.getVertexScore(2),0,.001);
+//    	Assert.assertEquals(bc.getVertexScore(3),0,.001);
+//    	Assert.assertEquals(bc.getVertexScore(4),0,.001);
+//
+//    	Assert.assertEquals(bc.getEdgeScore(graph.findEdge(0,1)),2,.001);
+//    	Assert.assertEquals(bc.getEdgeScore(graph.findEdge(1,2)),3,.001);
+//    	Assert.assertEquals(bc.getEdgeScore(graph.findEdge(3,1)),2,.001);
+//    	Assert.assertEquals(bc.getEdgeScore(graph.findEdge(4,2)),1,.001);
+//    }
+    
+    public void testWeighted()
+    {
+    	Graph<Integer, Character> graph = new DirectedSparseGraph<Integer, Character>();
+    	
+    	for(int i=0; i<5; i++) 
+    		graph.addVertex(i);
+
+    	char edge='a';
+    	graph.addEdge(edge++, 0,1);
+    	graph.addEdge(edge++, 0,2);
+    	graph.addEdge(edge++, 2,3);
+    	graph.addEdge(edge++, 3,1);
+    	graph.addEdge(edge++, 1,4);
+
+    	final int weights[] = {1, 1, 1, 1, 1};
+    	
+    	Function<Character, Integer> edge_weights = new Function<Character, Integer>()
+    	{
+			public Integer apply(Character arg0) { return weights[arg0 - 'a']; }
+    	};
+    	
+    	BetweennessCentrality<Integer,Character> bc = 
+    		new BetweennessCentrality<Integer,Character>(graph, edge_weights);
+
+//    	System.out.println("scoring");
+//    	System.out.println("(weighted)");
+//    	System.out.println("vertices:");
+//    	for (int i = 0; i < graph.getVertexCount(); i++) 
+//    		System.out.println(String.format("%d: %f", i, bc.getVertexScore(i)));
+//    	System.out.println("edges:");
+//    	for (int i = 0; i < graph.getEdgeCount(); i++) 
+//    	{
+//    		char e = (char)(i + 'a');
+//    		System.out.println(String.format("%c: (weight: %d), %f", e, 
+//    				edge_weights.apply(e), bc.getEdgeScore(e)));
+//    	}
+    }
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestHITS.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestHITS.java
new file mode 100644
index 0000000..76cccd2
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestHITS.java
@@ -0,0 +1,119 @@
+/*
+* Copyright (c) 2003, The JUNG Authors
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.scoring;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+
+
+/**
+ * @author Scott White
+ * @author Tom Nelson - adapted to jung2
+ */
+public class TestHITS extends TestCase {
+
+	DirectedGraph<Number,Number> graph;
+	
+    public static Test suite() {
+        return new TestSuite(TestHITS.class);
+    }
+
+    @Override
+    protected void setUp() {
+        graph = new DirectedSparseMultigraph<Number,Number>();
+        for(int i=0; i<5; i++) {
+        	graph.addVertex(i);
+        }
+
+        int j=0;
+        graph.addEdge(j++, 0, 1);
+        graph.addEdge(j++, 1, 2);
+        graph.addEdge(j++, 2, 3);
+        graph.addEdge(j++, 3, 0);
+        graph.addEdge(j++, 2, 1);
+    }
+
+    public void testRanker() {
+
+        HITS<Number,Number> ranker = new HITS<Number,Number>(graph);
+        for (int i = 0; i < 10; i++)
+        {
+            ranker.step();
+//            // check hub scores in terms of previous authority scores
+//            Assert.assertEquals(t.transform(0).hub, 
+//            		0.5*ranker.getAuthScore(1) + 0.2*ranker.getAuthScore(4));
+//            Assert.assertEquals(t.transform(1).hub, 
+//            		ranker.getAuthScore(2) + 0.2*ranker.getAuthScore(4));
+//            Assert.assertEquals(t.transform(2).hub, 
+//            		0.5*ranker.getAuthScore(1) + ranker.getAuthScore(3) + 0.2*ranker.getAuthScore(4));
+//            Assert.assertEquals(t.transform(3).hub, 
+//            		ranker.getAuthScore(0) + 0.2*ranker.getAuthScore(4));
+//            Assert.assertEquals(t.transform(4).hub, 
+//            		0.2*ranker.getAuthScore(4));
+//            
+//            // check authority scores in terms of previous hub scores
+//            Assert.assertEquals(t.transform(0).authority, 
+//            		ranker.getVertexScore(3) + 0.2*ranker.getVertexScore(4));
+//            Assert.assertEquals(t.transform(1).authority, 
+//            		ranker.getVertexScore(0) + 0.5 * ranker.getVertexScore(2) + 0.2*ranker.getVertexScore(4));
+//            Assert.assertEquals(t.transform(2).authority, 
+//            		ranker.getVertexScore(1) + 0.2*ranker.getVertexScore(4));
+//            Assert.assertEquals(t.transform(3).authority, 
+//            		0.5*ranker.getVertexScore(2) + 0.2*ranker.getVertexScore(4));
+//            Assert.assertEquals(t.transform(4).authority, 
+//            		0.2*ranker.getVertexScore(4));
+//            
+            // verify that sums of each scores are 1.0
+            double auth_sum = 0;
+            double hub_sum = 0;
+            for (int j = 0; j < 5; j++)
+            {
+//                auth_sum += ranker.getAuthScore(j);
+//                hub_sum += ranker.getVertexScore(j);
+//            	auth_sum += (ranker.getAuthScore(j) * ranker.getAuthScore(j));
+//            	hub_sum += (ranker.getVertexScore(j) * ranker.getVertexScore(j));
+            	HITS.Scores score = ranker.getVertexScore(j);
+            	auth_sum += score.authority * score.authority;
+            	hub_sum += score.hub * score.hub;
+            }
+            Assert.assertEquals(auth_sum, 1.0, .0001);
+            Assert.assertEquals(hub_sum, 1.0, 0.0001);
+        }
+        
+        ranker.evaluate();
+
+        Assert.assertEquals(ranker.getVertexScore(0).authority, 0, .0001);  
+        Assert.assertEquals(ranker.getVertexScore(1).authority, 0.8507, .001);
+        Assert.assertEquals(ranker.getVertexScore(2).authority, 0.0, .0001);
+        Assert.assertEquals(ranker.getVertexScore(3).authority, 0.5257, .001);
+
+        Assert.assertEquals(ranker.getVertexScore(0).hub, 0.5257, .001);
+        Assert.assertEquals(ranker.getVertexScore(1).hub, 0.0, .0001);
+        Assert.assertEquals(ranker.getVertexScore(2).hub, 0.8507, .0001);
+        Assert.assertEquals(ranker.getVertexScore(3).hub, 0.0, .0001);
+
+        // the values below assume scores sum to 1 
+        // (rather than that sum of squares of scores sum to 1)
+//        Assert.assertEquals(ranker.getVertexScore(0).authority, 0, .0001);  
+//        Assert.assertEquals(ranker.getVertexScore(1).authority, 0.618, .001);
+//        Assert.assertEquals(ranker.getVertexScore(2).authority, 0.0, .0001);
+//        Assert.assertEquals(ranker.getVertexScore(3).authority, 0.3819, .001);
+//
+//        Assert.assertEquals(ranker.getVertexScore(0).hub, 0.38196, .001);
+//        Assert.assertEquals(ranker.getVertexScore(1).hub, 0.0, .0001);
+//        Assert.assertEquals(ranker.getVertexScore(2).hub, 0.618, .0001);
+//        Assert.assertEquals(ranker.getVertexScore(3).hub, 0.0, .0001);
+    }
+
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestHITSWithPriors.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestHITSWithPriors.java
new file mode 100644
index 0000000..af64043
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestHITSWithPriors.java
@@ -0,0 +1,77 @@
+/*
+* Copyright (c) 2003, The JUNG Authors
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.scoring;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import edu.uci.ics.jung.algorithms.scoring.util.ScoringUtils;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+
+
+/**
+ * Tests HITSWithPriors.
+ */
+public class TestHITSWithPriors extends TestCase {
+
+	DirectedGraph<Number,Number> graph;
+	Set<Number> roots;
+	
+    public static Test suite() {
+        return new TestSuite(TestHITSWithPriors.class);
+    }
+
+    @Override
+    protected void setUp() {
+    	graph = new DirectedSparseMultigraph<Number,Number>();
+    	for(int i=0; i<4; i++) {
+    		graph.addVertex(i);
+    	}
+    	int j=0;
+    	graph.addEdge(j++, 0, 1);
+    	graph.addEdge(j++, 1, 2);
+    	graph.addEdge(j++, 2, 3);
+    	graph.addEdge(j++, 3, 0);
+    	graph.addEdge(j++, 2, 1);
+
+        roots = new HashSet<Number>();
+        roots.add(2);
+    }
+
+    public void testRankings() {
+
+        HITSWithPriors<Number,Number> ranker = 
+            new HITSWithPriors<Number,Number>(graph, ScoringUtils.getHITSUniformRootPrior(roots), 0.3);
+        ranker.evaluate();
+        
+        double[] expected_auth = {0.0, 0.765, 0.365, 0.530};
+        double[] expected_hub = {0.398, 0.190, 0.897, 0.0};
+
+        double hub_sum = 0;
+        double auth_sum = 0;
+        for (Number n : graph.getVertices())
+        {
+            int i = n.intValue();
+            double auth = ranker.getVertexScore(i).authority;
+            double hub = ranker.getVertexScore(i).hub;
+            Assert.assertEquals(auth, expected_auth[i], 0.001);
+            Assert.assertEquals(hub, expected_hub[i], 0.001);
+            hub_sum += hub * hub;
+            auth_sum += auth * auth;
+        }
+        Assert.assertEquals(1.0, hub_sum, 0.001);
+        Assert.assertEquals(1.0, auth_sum, 0.001);
+    }
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestKStepMarkov.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestKStepMarkov.java
new file mode 100644
index 0000000..3d5414d
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestKStepMarkov.java
@@ -0,0 +1,70 @@
+package edu.uci.ics.jung.algorithms.scoring;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.scoring.util.ScoringUtils;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+
+public class TestKStepMarkov extends TestCase 
+{
+	DirectedGraph<Number,Number> mGraph;
+    double[][] mTransitionMatrix;
+    Map<Number,Number> edgeWeights = new HashMap<Number,Number>();
+
+    @Override
+    protected void setUp()
+    {
+        mGraph = new DirectedSparseMultigraph<Number,Number>();
+        mTransitionMatrix = new double[][]
+           {{0.0, 0.5, 0.5},
+            {1.0/3.0, 0.0, 2.0/3.0},
+            {1.0/3.0, 2.0/3.0, 0.0}};
+
+        for (int i = 0; i < mTransitionMatrix.length; i++)
+        	mGraph.addVertex(i);
+
+        for (int i = 0; i < mTransitionMatrix.length; i++) {
+            for (int j = 0; j < mTransitionMatrix[i].length; j++)
+            {
+                if (mTransitionMatrix[i][j] > 0)
+                {
+                	int edge = i*mTransitionMatrix.length+j;
+                	mGraph.addEdge(edge, i, j);
+                	edgeWeights.put(edge, mTransitionMatrix[i][j]);
+                }
+            }
+        }
+    }
+
+    public void testRanker() {
+
+        Set<Number> priors = new HashSet<Number>();
+        priors.add(1);
+        priors.add(2);
+        KStepMarkov<Number,Number> ranker = 
+        	new KStepMarkov<Number,Number>(mGraph, Functions.forMap(edgeWeights), 
+        			ScoringUtils.getUniformRootPrior(priors),2);
+//        ranker.evaluate();
+//        System.out.println(ranker.getIterations());
+
+        for (int i = 0; i < 10; i++) 
+        {
+//            System.out.println(ranker.getIterations());
+//	        for (Number n : mGraph.getVertices())
+//	        	System.out.println(n + ": " + ranker.getVertexScore(n));
+	        ranker.step();
+        }
+//        List<Ranking<?>> rankings = ranker.getRankings();
+//        System.out.println("New version:");
+//        System.out.println(rankings);
+    }
+
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestPageRank.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestPageRank.java
new file mode 100644
index 0000000..4a9d362
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestPageRank.java
@@ -0,0 +1,80 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.scoring;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * @author Joshua O'Madadhain
+ */
+public class TestPageRank extends TestCase {
+	
+	private Map<Integer,Number> edgeWeights;
+	private DirectedGraph<Integer,Integer> graph;
+	private Supplier<Integer> edgeFactory;
+	
+    public static Test suite() {
+        return new TestSuite(TestPageRank.class);
+    }
+
+    @Override
+    protected void setUp() {
+    	edgeWeights = new HashMap<Integer,Number>();
+    	edgeFactory = new Supplier<Integer>() {
+    		int i=0;
+			public Integer get() {
+				return i++;
+			}};
+    }
+
+    private void addEdge(Graph<Integer,Integer> G, Integer v1, Integer v2, double weight) {
+    	Integer edge = edgeFactory.get();
+    	graph.addEdge(edge, v1, v2);
+    	edgeWeights.put(edge, weight);
+    }
+
+    public void testRanker() {
+    	graph = new DirectedSparseMultigraph<Integer,Integer>();
+    	for(int i=0; i<4; i++) {
+    		graph.addVertex(i);
+    	}
+        addEdge(graph,0,1,1.0);
+        addEdge(graph,1,2,1.0);
+        addEdge(graph,2,3,0.5);
+        addEdge(graph,3,1,1.0);
+        addEdge(graph,2,1,0.5);
+
+        PageRankWithPriors<Integer, Integer> pr = new PageRank<Integer, Integer>(graph, Functions.forMap(edgeWeights), 0);
+        pr.evaluate();
+        
+        Assert.assertEquals(pr.getVertexScore(0), 0.0, pr.getTolerance());
+        Assert.assertEquals(pr.getVertexScore(1), 0.4, pr.getTolerance());
+        Assert.assertEquals(pr.getVertexScore(2), 0.4, pr.getTolerance());
+        Assert.assertEquals(pr.getVertexScore(3), 0.2, pr.getTolerance());
+
+//        Assert.assertTrue(NumericalPrecision.equal(((Ranking)ranker.getRankings().get(0)).rankScore,0.4,.001));
+//        Assert.assertTrue(NumericalPrecision.equal(((Ranking)ranker.getRankings().get(1)).rankScore,0.4,.001));
+//        Assert.assertTrue(NumericalPrecision.equal(((Ranking)ranker.getRankings().get(2)).rankScore,0.2,.001));
+//        Assert.assertTrue(NumericalPrecision.equal(((Ranking)ranker.getRankings().get(3)).rankScore,0,.001));
+    }
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestPageRankWithPriors.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestPageRankWithPriors.java
new file mode 100644
index 0000000..cb6756c
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestPageRankWithPriors.java
@@ -0,0 +1,91 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.scoring;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.scoring.util.ScoringUtils;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Graph;
+
+
+/**
+ * @author Scott White
+ */
+public class TestPageRankWithPriors extends TestCase {
+
+//	private Map<Integer,Number> edgeWeights;
+	private DirectedGraph<Integer,Integer> graph;
+	private Supplier<Integer> edgeFactory;
+
+    public static Test suite() {
+        return new TestSuite(TestPageRankWithPriors.class);
+    }
+
+    @Override
+    protected void setUp() {
+//    	edgeWeights = new HashMap<Integer,Number>();
+    	edgeFactory = new Supplier<Integer>() {
+    		int i=0;
+			public Integer get() {
+				return i++;
+			}};
+    }
+
+    private void addEdge(Graph<Integer, Integer> G, Integer v1, Integer v2)
+    {
+    	Integer edge = edgeFactory.get();
+    	graph.addEdge(edge, v1, v2);
+//    	edgeWeights.put(edge, weight);
+    }
+
+    public void testGraphScoring() {
+    	graph = new DirectedSparseMultigraph<Integer,Integer>();
+
+    	double[] expected_score = new double[]{0.1157, 0.2463, 0.4724, 0.1653};
+    	
+    	for(int i=0; i<4; i++) {
+    		graph.addVertex(i);
+    	}
+        addEdge(graph,0,1);
+        addEdge(graph,1,2);
+        addEdge(graph,2,3);
+        addEdge(graph,3,0);
+        addEdge(graph,2,1);
+
+        Set<Integer> priors = new HashSet<Integer>();
+        priors.add(2);
+
+        PageRankWithPriors<Integer, Integer> pr = 
+            new PageRankWithPriors<Integer, Integer>(graph, ScoringUtils.getUniformRootPrior(priors), 0.3);
+        pr.evaluate();
+
+        double score_sum = 0;
+        for (int i = 0; i < graph.getVertexCount(); i++)
+        {
+        	double score = pr.getVertexScore(i);
+        	Assert.assertEquals(expected_score[i], score, pr.getTolerance());
+        	score_sum += score;
+        }
+        Assert.assertEquals(1.0, score_sum, pr.getTolerance() * graph.getVertexCount());
+    }
+
+    public void testHypergraphScoring() {
+    }
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestVoltageScore.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestVoltageScore.java
new file mode 100644
index 0000000..907bbb6
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/scoring/TestVoltageScore.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Jul 14, 2008
+ * 
+ */
+package edu.uci.ics.jung.algorithms.scoring;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedSparseMultigraph;
+
+/**
+ * @author jrtom
+ *
+ */
+public class TestVoltageScore extends TestCase 
+{
+    protected Graph<Number,Number> g;
+    
+    @Override
+    public void setUp() {
+        g = new UndirectedSparseMultigraph<Number,Number>();
+        for (int i = 0; i < 7; i++) {
+        	g.addVertex(i);
+        }
+
+        int j = 0;
+        g.addEdge(j++,0,1);
+        g.addEdge(j++,0,2);
+        g.addEdge(j++,1,3);
+        g.addEdge(j++,2,3);
+        g.addEdge(j++,3,4);
+        g.addEdge(j++,3,5);
+        g.addEdge(j++,4,6);
+        g.addEdge(j++,5,6);
+    }
+    
+    public final void testCalculateVoltagesSourceTarget() {
+        VoltageScorer<Number,Number> vr = new VoltageScorer<Number,Number>(g, Functions.<Number>constant(1), 0, 6);
+        double[] voltages = {1.0, 0.75, 0.75, 0.5, 0.25, 0.25, 0};
+        
+        vr.evaluate();
+        for (int i = 0; i < 7; i++) {
+            assertEquals(vr.getVertexScore(i), voltages[i], 0.01);
+        }
+    }
+    
+    public final void testCalculateVoltagesSourcesTargets()
+    {
+        Map<Number,Number> sources = new HashMap<Number,Number>();
+        sources.put(0, new Double(1.0));
+        sources.put(1, new Double(0.5));
+        Set<Number> sinks = new HashSet<Number>();
+        sinks.add(6);
+        sinks.add(5);
+        VoltageScorer<Number,Number> vr = 
+        	new VoltageScorer<Number,Number>(g, Functions.constant(1), sources, sinks);
+        double[] voltages = {1.0, 0.5, 0.66, 0.33, 0.16, 0, 0};
+        
+        vr.evaluate();
+        for (int i = 0; i < 7; i++) {
+            assertEquals(vr.getVertexScore(i), voltages[i], 0.01);
+        }
+    }
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/TestBFSDistanceLabeler.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/TestBFSDistanceLabeler.java
new file mode 100644
index 0000000..93375a8
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/TestBFSDistanceLabeler.java
@@ -0,0 +1,67 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import edu.uci.ics.jung.algorithms.shortestpath.BFSDistanceLabeler;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedSparseMultigraph;
+
+/**
+ * @author Scott White, adapted to jung2 by Tom Nelson
+ */
+public class TestBFSDistanceLabeler extends TestCase {
+	public static Test suite() {
+		return new TestSuite(TestBFSDistanceLabeler.class);
+	}
+
+	@Override
+  protected void setUp() {
+
+	}
+
+	public void test() {
+        Graph<Number,Number> graph = new UndirectedSparseMultigraph<Number,Number>();
+        for(int i=0; i<6; i++) {
+        	graph.addVertex(i);
+        }
+        int j = 0;
+        graph.addEdge(j++,0,1);
+        graph.addEdge(j++,0,5);
+        graph.addEdge(j++,0,3);
+        graph.addEdge(j++,0,4);
+        graph.addEdge(j++,1,5);
+        graph.addEdge(j++,3,4);
+        graph.addEdge(j++,3,2);
+        graph.addEdge(j++,5,2);
+        Number root = 0;
+
+		BFSDistanceLabeler<Number,Number> labeler = new BFSDistanceLabeler<Number,Number>();
+		labeler.labelDistances(graph,root);
+
+		Assert.assertEquals(labeler.getPredecessors(root).size(),0);
+        Assert.assertEquals(labeler.getPredecessors(1).size(),1);
+        Assert.assertEquals(labeler.getPredecessors(2).size(),2);
+        Assert.assertEquals(labeler.getPredecessors(3).size(),1);
+        Assert.assertEquals(labeler.getPredecessors(4).size(),1);
+        Assert.assertEquals(labeler.getPredecessors(5).size(),1);
+
+        Assert.assertEquals(labeler.getDistance(graph,0),0);
+        Assert.assertEquals(labeler.getDistance(graph,1),1);
+        Assert.assertEquals(labeler.getDistance(graph,2),2);
+        Assert.assertEquals(labeler.getDistance(graph,3),1);
+        Assert.assertEquals(labeler.getDistance(graph,4),1);
+        Assert.assertEquals(labeler.getDistance(graph,5),1);
+
+	}
+}
\ No newline at end of file
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/TestPrimMinimumSpanningTree.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/TestPrimMinimumSpanningTree.java
new file mode 100644
index 0000000..184a844
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/TestPrimMinimumSpanningTree.java
@@ -0,0 +1,62 @@
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+import junit.framework.TestCase;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.DelegateTree;
+import edu.uci.ics.jung.graph.Tree;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.UndirectedSparseMultigraph;
+
+public class TestPrimMinimumSpanningTree extends TestCase {
+	
+	public void testSimpleTree() {
+		Tree<String,Integer> tree = new DelegateTree<String,Integer>();
+		tree.addVertex("A");
+		tree.addEdge(0,"A","B0");
+		tree.addEdge(1,"A","B1");
+		
+//		System.err.println("tree = "+tree);
+		PrimMinimumSpanningTree<String,Integer> pmst = 
+			new PrimMinimumSpanningTree<String,Integer>(DelegateTree.<String,Integer>getFactory());
+		
+//		Graph<String,Integer> mst = 
+		pmst.apply(tree);
+//		System.err.println("mst = "+mst);
+		
+//		assertEquals(tree.getVertices(), mst.getVertices());
+//		assertEquals(tree.getEdges(), mst.getEdges());
+		
+	}
+	
+	public void testDAG() {
+		DirectedGraph<String,Integer> graph = new DirectedSparseMultigraph<String,Integer>();
+		graph.addVertex("B0");
+		graph.addEdge(0, "A", "B0");
+		graph.addEdge(1, "A", "B1");
+//		System.err.println("graph = "+graph);
+		PrimMinimumSpanningTree<String,Integer> pmst = 
+			new PrimMinimumSpanningTree<String,Integer>(DelegateTree.<String,Integer>getFactory());
+		
+//		Graph<String,Integer> mst = 
+		pmst.apply(graph);
+//		System.err.println("mst = "+mst);
+		
+	}
+
+	public void testUAG() {
+		UndirectedGraph<String,Integer> graph = new UndirectedSparseMultigraph<String,Integer>();
+		graph.addVertex("B0");
+		graph.addEdge(0, "A", "B0");
+		graph.addEdge(1, "A", "B1");
+//		System.err.println("graph = "+graph);
+		PrimMinimumSpanningTree<String,Integer> pmst = 
+			new PrimMinimumSpanningTree<String,Integer>(DelegateTree.<String,Integer>getFactory());
+		
+//		Graph<String,Integer> mst = 
+		pmst.apply(graph);
+//		System.err.println("mst = "+mst);
+		
+	}
+
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/TestShortestPath.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/TestShortestPath.java
new file mode 100644
index 0000000..309f2eb
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/TestShortestPath.java
@@ -0,0 +1,528 @@
+/*
+ * Created on Aug 22, 2003
+ *
+ */
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+import com.google.common.collect.BiMap;
+
+import edu.uci.ics.jung.algorithms.util.Indexer;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.UndirectedSparseMultigraph;
+
+
+/**
+ * @author Joshua O'Madadhain
+ */
+public class TestShortestPath extends TestCase
+{
+    private DirectedGraph<String,Integer> dg;  
+    private UndirectedGraph<String,Integer> ug;
+    // graph based on Weiss, _Data Structures and Algorithm Analysis_,
+    // 1992, p. 292
+    private static int[][] edges = 
+        {{1,2,2}, {1,4,1}, // 0, 1
+            {2,4,3}, {2,5,10}, // 2, 3
+            {3,1,4}, {3,6,5},  // 4, 5
+            {4,3,2}, {4,5,2}, {4,6,8}, {4,7,4}, // 6,7,8,9
+            {5,7,6}, // 10
+            {7,6,1}, // 11
+            {8,9,4}, // (12) these three edges define a second connected component
+            {9,10,1}, // 13
+            {10,8,2}}; // 14
+
+    private static Integer[][] ug_incomingEdges = 
+    {
+        {null, new Integer(0), new Integer(6), new Integer(1), new Integer(7), new Integer(11), new Integer(9), null, null, null},
+        {new Integer(0), null, new Integer(6), new Integer(2), new Integer(7), new Integer(11), new Integer(9), null, null, null},
+        {new Integer(1), new Integer(2), null, new Integer(6), new Integer(7), new Integer(5), new Integer(9), null, null, null},
+        {new Integer(1), new Integer(2), new Integer(6), null, new Integer(7), new Integer(11), new Integer(9), null, null, null},
+        {new Integer(1), new Integer(2), new Integer(6), new Integer(7), null, new Integer(11), new Integer(10), null, null, null},
+        {new Integer(1), new Integer(2), new Integer(5), new Integer(9), new Integer(10), null, new Integer(11), null, null, null},
+        {new Integer(1), new Integer(2), new Integer(5), new Integer(9), new Integer(10), new Integer(11), null, null, null, null},
+        {null, null, null, null, null, null, null, null, new Integer(13), new Integer(14)},
+        {null, null, null, null, null, null, null, new Integer(14), null, new Integer(13)},
+        {null, null, null, null, null, null, null, new Integer(14), new Integer(13), null},
+    };      
+    
+    private static Integer[][] dg_incomingEdges = 
+        {
+            {null, new Integer(0), new Integer(6), new Integer(1), new Integer(7), new Integer(11), new Integer(9), null, null, null},
+            {new Integer(4), null, new Integer(6), new Integer(2), new Integer(7), new Integer(11), new Integer(9), null, null, null},
+            {new Integer(4), new Integer(0), null, new Integer(1), new Integer(7), new Integer(5), new Integer(9), null, null, null},
+            {new Integer(4), new Integer(0), new Integer(6), null, new Integer(7), new Integer(11), new Integer(9), null, null, null},
+            {null, null, null, null, null, new Integer(11), new Integer(10), null, null, null},
+            {null, null, null, null, null, null, null, null, null, null},
+            {null, null, null, null, null, new Integer(11), null, null, null, null},
+            {null, null, null, null, null, null, null, null, new Integer(12), new Integer(13)},
+            {null, null, null, null, null, null, null, new Integer(14), null, new Integer(13)},
+            {null, null, null, null, null, null, null, new Integer(14), new Integer(12), null}
+    };      
+    
+    private static double[][] dg_distances = 
+    {
+        {0, 2, 3, 1, 3, 6, 5, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {9, 0, 5, 3, 5, 8, 7, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {4, 6, 0, 5, 7, 5, 9, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {6, 8, 2, 0, 2, 5, 4, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 0, 7, 6, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 0, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1, 0, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 0, 4, 5},
+        {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 3, 0, 1},
+        {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 2, 6, 0}
+    };
+
+    private static double[][] ug_distances = 
+    {
+        {0, 2, 3, 1, 3, 6, 5, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {2, 0, 5, 3, 5, 8, 7, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {3, 5, 0, 2, 4, 5, 6, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {1, 3, 2, 0, 2, 5, 4, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {3, 5, 4, 2, 0, 7, 6, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {6, 8, 5, 5, 7, 0, 1, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {5, 7, 6, 4, 6, 1, 0, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY},
+        {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 0, 3, 2},
+        {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 3, 0, 1},
+        {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 2, 1, 0}
+    };
+    
+
+    private static Integer[][] shortestPaths1 = 
+    {
+    	null,
+        {new Integer(0)},
+        {new Integer(1), new Integer(6)},
+        {new Integer(1)},
+        {new Integer(1), new Integer(7)},
+        {new Integer(1), new Integer(9), new Integer(11)},
+        {new Integer(1), new Integer(9)},
+        null,
+        null,
+        null
+    };
+    
+    private Map<Graph<String,Integer>,Integer[]> edgeArrays;
+    
+    private Map<Integer,Number> edgeWeights;
+    
+    private Function<Integer,Number> nev;
+    
+    private Supplier<String> vertexFactoryDG =
+    	new Supplier<String>() {
+    	int count = 0;
+    	public String get() {
+    		return "V"+count++;
+    	}};
+    	private Supplier<String> vertexFactoryUG =
+    		new Supplier<String>() {
+    		int count = 0;
+    		public String get() {
+    			return "U"+count++;
+    		}};
+    		
+    BiMap<String,Integer> did;
+    BiMap<String,Integer> uid;
+
+    @Override
+    protected void setUp() {
+    	edgeWeights = new HashMap<Integer,Number>();
+        nev = Functions.<Integer,Number>forMap(edgeWeights);
+		dg = new DirectedSparseMultigraph<String,Integer>();
+		for(int i=0; i<dg_distances.length; i++) {
+			dg.addVertex(vertexFactoryDG.get());
+		}
+		did = Indexer.<String>create(dg.getVertices(), 1);
+        Integer[] dg_array = new Integer[edges.length];
+		addEdges(dg, did, dg_array);
+        
+        ug = new UndirectedSparseMultigraph<String,Integer>();
+		for(int i=0; i<ug_distances.length; i++) {
+			ug.addVertex(vertexFactoryUG.get());
+		}
+        uid = Indexer.<String>create(ug.getVertices(),1);
+//        GraphUtils.addVertices(ug, ug_distances.length);
+//        Indexer.newIndexer(ug, 1);
+        Integer[] ug_array = new Integer[edges.length];
+        addEdges(ug, uid, ug_array);
+        
+        edgeArrays = new HashMap<Graph<String,Integer>,Integer[]>();
+        edgeArrays.put(dg, dg_array);
+        edgeArrays.put(ug, ug_array);
+    }
+    
+    @Override
+    protected void tearDown() throws Exception {
+    }
+
+    public void exceptionTest(Graph<String,Integer> g, BiMap<String,Integer> indexer, int index)
+    {
+        DijkstraShortestPath<String,Integer> dsp = 
+        	new DijkstraShortestPath<String,Integer>(g, nev);
+//        Indexer id = Indexer.getIndexer(g);
+        String start = indexer.inverse().get(index);
+        Integer e = null;
+
+        String v = "NOT IN GRAPH";
+        
+        try
+        {
+            dsp.getDistance(start, v);
+            fail("getDistance(): illegal destination vertex");
+        }
+        catch (IllegalArgumentException iae) {}
+        try
+        {
+            dsp.getDistance(v, start);
+            fail("getDistance(): illegal source vertex");
+        }
+        catch (IllegalArgumentException iae) {}
+        try
+        {
+            dsp.getDistanceMap(v, 1);
+            fail("getDistanceMap(): illegal source vertex");
+        }
+        catch (IllegalArgumentException iae) {}
+        try
+        {
+            dsp.getDistanceMap(start, 0);
+            fail("getDistanceMap(): too few vertices requested");
+        }
+        catch (IllegalArgumentException iae) {}
+        try
+        {
+            dsp.getDistanceMap(start, g.getVertexCount()+1);
+            fail("getDistanceMap(): too many vertices requested");
+        }
+        catch (IllegalArgumentException iae) {}
+
+        try
+        {
+            dsp.getIncomingEdge(start, v);
+            fail("getIncomingEdge(): illegal destination vertex");
+        }
+        catch (IllegalArgumentException iae) {}
+        try
+        {
+            dsp.getIncomingEdge(v, start);
+            fail("getIncomingEdge(): illegal source vertex");
+        }
+        catch (IllegalArgumentException iae) {}
+        try
+        {
+            dsp.getIncomingEdgeMap(v, 1);
+            fail("getIncomingEdgeMap(): illegal source vertex");
+        }
+        catch (IllegalArgumentException iae) {}
+        try
+        {
+            dsp.getIncomingEdgeMap(start, 0);
+            fail("getIncomingEdgeMap(): too few vertices requested");
+        }
+        catch (IllegalArgumentException iae) {}
+        try
+        {
+            dsp.getDistanceMap(start, g.getVertexCount()+1);
+            fail("getIncomingEdgeMap(): too many vertices requested");
+        }
+        catch (IllegalArgumentException iae) {}
+
+        try
+        {
+            // test negative edge weight exception
+            String v1 = indexer.inverse().get(1);
+            String v2 = indexer.inverse().get(7);
+            e = g.getEdgeCount()+1;
+         	g.addEdge(e, v1, v2);
+         	edgeWeights.put(e, -2);
+//            e.addUserDatum("weight", new Double(-2), UserData.REMOVE);
+            dsp.reset();
+            dsp.getDistanceMap(start);
+//            for (Iterator it = g.getEdges().iterator(); it.hasNext(); )
+//            {
+//                Edge edge = (Edge)it.next();
+//                double weight = ((Number)edge.getUserDatum("weight")).doubleValue();
+//                Pair p = edge.getEndpoints();
+//                int i = id.getIndex((Vertex)p.getFirst());
+//                int j = id.getIndex((Vertex)p.getSecond());
+//                System.out.print("(" + i + "," + j + "): " + weight);
+//                if (weight < 0)
+//                    System.out.print(" *******");
+//                System.out.println();
+//            }
+            fail("DijkstraShortestPath should not accept negative edge weights");
+        }
+        catch (IllegalArgumentException iae) 
+        {
+            g.removeEdge(e);
+        }
+    }
+    
+    public void testDijkstra()
+    {
+        setUp();
+        exceptionTest(dg, did, 1);
+        
+        setUp();
+        exceptionTest(ug, uid, 1);
+        
+        setUp();
+        getPathTest(dg, did, 1);
+        
+        setUp();
+        getPathTest(ug, uid, 1);
+                
+        for (int i = 1; i <= dg_distances.length; i++)
+        {
+            setUp();
+            weightedTest(dg, did, i, true);
+
+            setUp();
+            weightedTest(dg, did, i, false);
+        }
+        
+        for (int i = 1; i <= ug_distances.length; i++)
+        {
+            setUp();
+            weightedTest(ug, uid, i, true);
+            
+            setUp();
+            weightedTest(ug, uid, i, false);
+        }
+        
+    }
+
+    private void getPathTest(Graph<String,Integer> g, BiMap<String,Integer> indexer, int index)
+    {
+        DijkstraShortestPath<String,Integer> dsp = 
+        	new DijkstraShortestPath<String,Integer>(g, nev);
+//        Indexer id = Indexer.getIndexer(g);
+        String start = indexer.inverse().get(index);
+        Integer[] edge_array = edgeArrays.get(g);
+        Integer[] incomingEdges1 = null;
+        if (g instanceof DirectedGraph)
+            incomingEdges1 = dg_incomingEdges[index-1];
+        if (g instanceof UndirectedGraph)
+            incomingEdges1 = ug_incomingEdges[index-1];
+        assertEquals(incomingEdges1.length, g.getVertexCount());
+        
+         // test getShortestPath(start, v)
+        dsp.reset();
+        for (int i = 1; i <= incomingEdges1.length; i++)
+        {
+          List<Integer> shortestPath = dsp.getPath(start, indexer.inverse().get(i));
+            Integer[] indices = shortestPaths1[i-1];
+            for (ListIterator<Integer> iter = shortestPath.listIterator(); iter.hasNext(); )
+            {
+                int j = iter.nextIndex();
+                Integer e = iter.next();
+                if (e != null)
+                    assertEquals(edge_array[indices[j].intValue()], e);
+                else
+                    assertNull(indices[j]);
+            }
+        }
+    }
+    
+    private void weightedTest(Graph<String,Integer> g, BiMap<String,Integer> indexer, int index, boolean cached) {
+//        Indexer id = Indexer.getIndexer(g);
+        String start = indexer.inverse().get(index);
+        double[] distances1 = null;
+        Integer[] incomingEdges1 = null;
+        if (g instanceof DirectedGraph)
+        {
+            distances1 = dg_distances[index-1];
+            incomingEdges1 = dg_incomingEdges[index-1];
+        }
+        if (g instanceof UndirectedGraph)
+        {    
+            distances1 = ug_distances[index-1];
+            incomingEdges1 = ug_incomingEdges[index-1];
+        }
+        assertEquals(distances1.length, g.getVertexCount());
+        assertEquals(incomingEdges1.length, g.getVertexCount());
+        DijkstraShortestPath<String,Integer> dsp = 
+        	new DijkstraShortestPath<String,Integer>(g, nev, cached);
+        Integer[] edge_array = edgeArrays.get(g);
+        
+        // test getDistance(start, v)
+        for (int i = 1; i <= distances1.length; i++) {
+            String v = indexer.inverse().get(i);
+            Number n = dsp.getDistance(start, v);
+            double d = distances1[i-1];
+            double dist;
+            if (n == null)
+                dist = Double.POSITIVE_INFINITY;
+            else
+                dist = n.doubleValue();
+            
+            assertEquals(d, dist, .001);
+        }
+
+        // test getIncomingEdge(start, v)
+        dsp.reset();
+        for (int i = 1; i <= incomingEdges1.length; i++)
+        {
+            String v = indexer.inverse().get(i);
+            Integer e = dsp.getIncomingEdge(start, v);
+            if (e != null)
+                assertEquals(edge_array[incomingEdges1[i-1].intValue()], e);
+            else
+                assertNull(incomingEdges1[i-1]);
+        }
+        
+        // test getDistanceMap(v)
+        dsp.reset();
+        Map<String,Number> distances = dsp.getDistanceMap(start);
+        assertTrue(distances.size() <= g.getVertexCount());
+        double d_prev = 0; // smallest possible distance
+        Set<String> reachable = new HashSet<String>();
+        for (Iterator<String> d_iter = distances.keySet().iterator(); d_iter.hasNext(); )
+        {
+            String cur = d_iter.next();
+            double d_cur = ((Double)distances.get(cur)).doubleValue();
+            assertTrue(d_cur >= d_prev);
+
+            d_prev = d_cur;
+            int i = indexer.get(cur);
+            assertEquals(distances1[i-1], d_cur, .001);
+            reachable.add(cur);
+        }
+        // make sure that non-reachable vertices have no entries
+        for (Iterator<String> v_iter = g.getVertices().iterator(); v_iter.hasNext(); )
+        {
+            String v = v_iter.next();
+            assertEquals(reachable.contains(v), distances.keySet().contains(v));
+        }
+        
+        // test getIncomingEdgeMap(v)
+        dsp.reset();
+        Map<String,Integer> incomingEdgeMap = dsp.getIncomingEdgeMap(start);
+        assertTrue(incomingEdgeMap.size() <= g.getVertexCount());
+        for (Iterator<String> e_iter = incomingEdgeMap.keySet().iterator(); e_iter.hasNext(); )
+        {
+            String v = e_iter.next();
+            Integer e = incomingEdgeMap.get(v);
+            int i = indexer.get(v);
+//            if (e != null)
+//            {    
+//                Pair endpoints = e.getEndpoints();
+//                int j = id.getIndex((Vertex)endpoints.getFirst());
+//                int k = id.getIndex((Vertex)endpoints.getSecond());
+//                System.out.print(i + ": (" + j + "," + k + ");  ");
+//            }
+//            else
+//                System.out.print(i + ": null;  ");
+            if (e != null)
+                assertEquals(edge_array[incomingEdges1[i-1].intValue()], e);
+            else
+                assertNull(incomingEdges1[i-1]);
+        }
+        
+        // test getDistanceMap(v, k)
+        dsp.reset();
+        for (int i = 1; i <= distances1.length; i++)
+        {
+            distances = dsp.getDistanceMap(start, i);
+            assertTrue(distances.size() <= i);
+            d_prev = 0; // smallest possible distance
+
+            reachable.clear();
+            for (Iterator<String> d_iter = distances.keySet().iterator(); d_iter.hasNext(); )
+            {
+                String cur = d_iter.next();
+                double d_cur = ((Double)distances.get(cur)).doubleValue();
+                assertTrue(d_cur >= d_prev);
+
+                d_prev = d_cur;
+                int j = indexer.get(cur);
+
+                assertEquals(distances1[j-1], d_cur, .001);
+                reachable.add(cur);
+            }
+            for (Iterator<String> v_iter = g.getVertices().iterator(); v_iter.hasNext(); )
+            {
+                String v = v_iter.next();
+                assertEquals(reachable.contains(v), distances.keySet().contains(v));
+            }
+        }
+                
+        // test getIncomingEdgeMap(v, k)
+        dsp.reset();
+        for (int i = 1; i <= incomingEdges1.length; i++)
+        {
+            incomingEdgeMap = dsp.getIncomingEdgeMap(start, i);
+            assertTrue(incomingEdgeMap.size() <= i);
+            for (Iterator<String> e_iter = incomingEdgeMap.keySet().iterator(); e_iter.hasNext(); )
+            {
+                String v = e_iter.next();
+                Integer e = incomingEdgeMap.get(v);
+                int j = indexer.get(v);
+                if (e != null)
+                    assertEquals(edge_array[incomingEdges1[j-1].intValue()], e);
+                else
+                    assertNull(incomingEdges1[j-1]);
+            }
+        }
+    }
+    
+    public void addEdges(Graph<String,Integer> g, BiMap<String,Integer> indexer, Integer[] edge_array)
+    {
+    	
+//        Indexer id = Indexer.getIndexer(g);
+        for (int i = 0; i < edges.length; i++)
+        {
+            int[] edge = edges[i];
+            Integer e = i;
+            g.addEdge(i, indexer.inverse().get(edge[0]), indexer.inverse().get(edge[1]));
+            edge_array[i] = e;
+            if (edge.length > 2) {
+            	edgeWeights.put(e, edge[2]);
+//                e.addUserDatum("weight", edge[2]);
+            }
+        }
+    }
+
+    
+//    private class UserDataEdgeWeight implements NumberEdgeValue
+//    {
+//        private Object ud_key;
+//        
+//        public UserDataEdgeWeight(Object key)
+//        {
+//            ud_key = key;
+//        }
+//        
+//		/**
+//		 * @see edu.uci.ics.jung.utils.NumberEdgeValue#getNumber(edu.uci.ics.jung.graph.ArchetypeEdge)
+//		 */
+//		public Number getNumber(ArchetypeEdge e)
+//		{
+//            return (Number)e.getUserDatum(ud_key);
+//		}
+//
+//		/**
+//		 * @see edu.uci.ics.jung.utils.NumberEdgeValue#setNumber(edu.uci.ics.jung.graph.ArchetypeEdge, java.lang.Number)
+//		 */
+//		public void setNumber(ArchetypeEdge e, Number n)
+//		{
+//            throw new UnsupportedOperationException();
+//		}
+//    }
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/TestUnweightedShortestPath.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/TestUnweightedShortestPath.java
new file mode 100644
index 0000000..89533ff
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/shortestpath/TestUnweightedShortestPath.java
@@ -0,0 +1,95 @@
+/*
+ * Created on Aug 22, 2003
+ *
+ */
+package edu.uci.ics.jung.algorithms.shortestpath;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.BiMap;
+
+import edu.uci.ics.jung.algorithms.util.Indexer;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.UndirectedSparseMultigraph;
+
+/**
+ * @author Scott White
+ */
+public class TestUnweightedShortestPath extends TestCase
+{
+    private Supplier<String> vertexFactory =
+    	new Supplier<String>() {
+    	int count = 0;
+    	public String get() {
+    		return "V"+count++;
+    	}};
+    	
+     private Supplier<Integer> edgeFactory =
+        	new Supplier<Integer>() {
+        	int count = 0;
+        	public Integer get() {
+        		return count++;
+        	}};
+    BiMap<String,Integer> id;
+
+    @Override
+    protected void setUp() {
+    }
+	public static Test suite()
+	{
+		return new TestSuite(TestUnweightedShortestPath.class);
+	}
+	
+	public void testUndirected() {
+		UndirectedGraph<String,Integer> ug = 
+			new UndirectedSparseMultigraph<String,Integer>();
+		for(int i=0; i<5; i++) {
+			ug.addVertex(vertexFactory.get());
+		}
+		id = Indexer.<String>create(ug.getVertices());
+
+//		GraphUtils.addVertices(ug,5);
+//		Indexer id = Indexer.getIndexer(ug);
+		ug.addEdge(edgeFactory.get(), id.inverse().get(0), id.inverse().get(1));
+		ug.addEdge(edgeFactory.get(), id.inverse().get(1), id.inverse().get(2));
+		ug.addEdge(edgeFactory.get(), id.inverse().get(2), id.inverse().get(3));
+		ug.addEdge(edgeFactory.get(), id.inverse().get(0), id.inverse().get(4));
+		ug.addEdge(edgeFactory.get(), id.inverse().get(4), id.inverse().get(3));
+		
+		UnweightedShortestPath<String,Integer> usp = 
+			new UnweightedShortestPath<String,Integer>(ug);
+		Assert.assertEquals(usp.getDistance(id.inverse().get(0),id.inverse().get(3)).intValue(),2);
+		Assert.assertEquals((usp.getDistanceMap(id.inverse().get(0)).get(id.inverse().get(3))).intValue(),2);
+		Assert.assertNull(usp.getIncomingEdgeMap(id.inverse().get(0)).get(id.inverse().get(0)));
+		Assert.assertNotNull(usp.getIncomingEdgeMap(id.inverse().get(0)).get(id.inverse().get(3)));
+	}
+	
+	public void testDirected() {
+			DirectedGraph<String,Integer> dg = 
+				new DirectedSparseMultigraph<String,Integer>();
+			for(int i=0; i<5; i++) {
+				dg.addVertex(vertexFactory.get());
+			}
+			id = Indexer.<String>create(dg.getVertices());
+			dg.addEdge(edgeFactory.get(), id.inverse().get(0), id.inverse().get(1));
+			dg.addEdge(edgeFactory.get(), id.inverse().get(1), id.inverse().get(2));
+			dg.addEdge(edgeFactory.get(), id.inverse().get(2), id.inverse().get(3));
+			dg.addEdge(edgeFactory.get(), id.inverse().get(0), id.inverse().get(4));
+			dg.addEdge(edgeFactory.get(), id.inverse().get(4), id.inverse().get(3));
+			dg.addEdge(edgeFactory.get(), id.inverse().get(3), id.inverse().get(0));
+		
+			UnweightedShortestPath<String,Integer> usp = 
+				new UnweightedShortestPath<String,Integer>(dg);
+			Assert.assertEquals(usp.getDistance(id.inverse().get(0),id.inverse().get(3)).intValue(),2);
+			Assert.assertEquals((usp.getDistanceMap(id.inverse().get(0)).get(id.inverse().get(3))).intValue(),2);
+			Assert.assertNull(usp.getIncomingEdgeMap(id.inverse().get(0)).get(id.inverse().get(0)));
+			Assert.assertNotNull(usp.getIncomingEdgeMap(id.inverse().get(0)).get(id.inverse().get(3)));
+
+		}
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/util/NotRandom.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/util/NotRandom.java
new file mode 100644
index 0000000..9d311e6
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/util/NotRandom.java
@@ -0,0 +1,60 @@
+package edu.uci.ics.jung.algorithms.util;
+
+import java.util.Random;
+
+/**
+ * A decidedly non-random extension of {@code Random} that may be useful 
+ * for testing random algorithms that accept an instance of {@code Random}
+ * as a parameter.  This algorithm maintains internal counters which are 
+ * incremented after each call, and returns values which are functions of
+ * those counter values.  Thus the output is not only deterministic (as is
+ * necessarily true of all software with no externalities) but precisely
+ * predictable in distribution.
+ * 
+ * @author Joshua O'Madadhain
+ */
+ at SuppressWarnings("serial")
+public class NotRandom extends Random 
+{
+	private int i = 0;
+	private int d = 0;
+	private int size = 100;
+	
+	/**
+	 * Creates an instance with the specified sample size.
+	 * @param size the sample size
+	 */
+	public NotRandom(int size)
+	{
+		this.size = size;
+	}
+	
+	/**
+	 * Returns the post-incremented value of the internal counter modulo n.
+	 */
+	@Override
+  public int nextInt(int n)
+	{
+		return i++ % n;
+	}
+	
+	/**
+	 * Returns the post-incremented value of the internal counter modulo 
+	 * {@code size}, divided by {@code size}.
+	 */
+	@Override
+  public double nextDouble()
+	{
+		return (d++ % size) / (double)size; 
+	}
+	
+	/**
+	 * Returns the post-incremented value of the internal counter modulo 
+	 * {@code size}, divided by {@code size}.
+	 */
+	@Override
+  public float nextFloat()
+	{
+		return (d++ % size) / (float)size; 
+	}
+}
diff --git a/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/util/TestWeightedChoice.java b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/util/TestWeightedChoice.java
new file mode 100644
index 0000000..3e89b3d
--- /dev/null
+++ b/jung-algorithms/src/test/java/edu/uci/ics/jung/algorithms/util/TestWeightedChoice.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2009, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Jan 13, 2009
+ * 
+ */
+package edu.uci.ics.jung.algorithms.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * @author jrtom
+ *
+ */
+public class TestWeightedChoice extends TestCase 
+{
+	private WeightedChoice<String> weighted_choice;
+	private Map<String, Double> item_weights = new HashMap<String, Double>();
+	private Map<String, Integer> item_counts = new HashMap<String, Integer>();
+	
+	@Override
+    public void tearDown()
+	{
+		item_weights.clear();
+		item_counts.clear();
+	}
+
+	private void initializeWeights(double[] weights)
+	{
+		item_weights.put("a", weights[0]);
+		item_weights.put("b", weights[1]);
+		item_weights.put("c", weights[2]);
+		item_weights.put("d", weights[3]);
+		
+		for (String key : item_weights.keySet())
+			item_counts.put(key, 0);
+
+	}
+
+	private void runWeightedChoice()
+	{
+		weighted_choice = new WeightedChoice<String>(item_weights, new NotRandom(100));
+		
+		int max_iterations = 10000;
+		for (int i = 0; i < max_iterations; i++)
+		{
+			String item = weighted_choice.nextItem();
+			int count = item_counts.get(item);
+			item_counts.put(item, count+1);
+		}
+		
+		for (String key : item_weights.keySet())
+			assertEquals((int)(item_weights.get(key) * max_iterations), 
+						item_counts.get(key).intValue());
+	}
+	
+	public void testUniform() 
+	{
+		initializeWeights(new double[]{0.25, 0.25, 0.25, 0.25});
+		
+		runWeightedChoice();
+	}
+	
+	public void testNonUniform()
+	{
+		initializeWeights(new double[]{0.45, 0.10, 0.13, 0.32});
+		
+		runWeightedChoice();
+	}
+}
diff --git a/jung-api-2.0.1-sources.jar b/jung-api-2.0.1-sources.jar
deleted file mode 100644
index 37b6b13..0000000
Binary files a/jung-api-2.0.1-sources.jar and /dev/null differ
diff --git a/jung-api/assembly.xml b/jung-api/assembly.xml
new file mode 100644
index 0000000..81fb092
--- /dev/null
+++ b/jung-api/assembly.xml
@@ -0,0 +1,19 @@
+<assembly>
+  <id>dependencies</id>
+  <formats>
+    <format>tar.gz</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <fileSets>
+    <fileSet>
+      <outputDirectory>/</outputDirectory>
+    </fileSet>
+  </fileSets>
+  <dependencySets>
+    <dependencySet>
+      <outputDirectory>/</outputDirectory>
+      <unpack>false</unpack>
+      <scope>runtime</scope>
+    </dependencySet>
+  </dependencySets>
+</assembly>
diff --git a/jung-api/pom.xml b/jung-api/pom.xml
new file mode 100644
index 0000000..56a2d24
--- /dev/null
+++ b/jung-api/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>net.sf.jung</groupId>
+    <artifactId>jung-parent</artifactId>
+    <version>2.1.1</version>
+  </parent>
+  <artifactId>jung-api</artifactId>
+  <name>JUNG - API</name>
+  <description>Graph interfaces used by the JUNG project</description>
+  
+  <dependencies>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+   	<plugins>
+ 	  <plugin>
+        <!-- TODO(cgruber) Break out an API compliance module  -->
+        <artifactId>maven-jar-plugin</artifactId>
+		<executions>
+		  <execution>
+		    <goals>
+		      <goal>test-jar</goal>
+		    </goals>
+		  </execution>
+		</executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/DirectedGraph.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/DirectedGraph.java
new file mode 100644
index 0000000..347cc1c
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/DirectedGraph.java
@@ -0,0 +1,24 @@
+/*
+ * Created on Oct 17, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+/**
+ * A tagging interface for implementations of <code>Graph</code> 
+ * that accept only directed edges.
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ * @param <V>   type specification for vertices
+ * @param <E>   type specification for edges
+ */
+public interface DirectedGraph<V,E> extends Graph<V,E> {
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/Forest.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/Forest.java
new file mode 100644
index 0000000..d937778
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/Forest.java
@@ -0,0 +1,96 @@
+package edu.uci.ics.jung.graph;
+
+import java.util.Collection;
+
+/**
+ * An interface for a graph which consists of a collection of rooted 
+ * directed acyclic graphs.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public interface Forest<V,E> extends DirectedGraph<V,E> {
+	
+    /**
+     * Returns a view of this graph as a collection of <code>Tree</code> instances.
+     * @return a view of this graph as a collection of <code>Tree</code>s
+     */
+	Collection<Tree<V,E>> getTrees();
+
+    /**
+     * Returns the parent of <code>vertex</code> in this tree.
+     * (If <code>vertex</code> is the root, returns <code>null</code>.)
+     * The parent of a vertex is defined as being its predecessor in the 
+     * (unique) shortest path from the root to this vertex.
+     * This is a convenience method which is equivalent to 
+     * <code>Graph.getPredecessors(vertex).iterator().next()</code>.
+     * 
+     * @param vertex the vertex whose parent is to be returned
+     * @return the parent of <code>vertex</code> in this tree
+     * @see Graph#getPredecessors(Object)
+     * @see #getParentEdge(Object)
+     */
+    public V getParent(V vertex);
+    
+    /**
+     * Returns the edge connecting <code>vertex</code> to its parent in
+     * this tree.
+     * (If <code>vertex</code> is the root, returns <code>null</code>.)
+     * The parent of a vertex is defined as being its predecessor in the 
+     * (unique) shortest path from the root to this vertex.
+     * This is a convenience method which is equivalent to 
+     * <code>Graph.getInEdges(vertex).iterator().next()</code>,
+     * and also to <code>Graph.findEdge(vertex, getParent(vertex))</code>.
+     * 
+     * @param vertex the vertex whose incoming edge is to be returned
+     * @return the edge connecting <code>vertex</code> to its parent, or 
+     * <code>null</code> if <code>vertex</code> is the root
+     * 
+     * @see Graph#getInEdges(Object)
+     * @see #getParent(Object)
+     */
+    public E getParentEdge(V vertex);
+    
+    /**
+     * Returns the children of <code>vertex</code> in this tree.
+     * The children of a vertex are defined as being the successors of
+     * that vertex on the respective (unique) shortest paths from the root to
+     * those vertices.
+     * This is syntactic (maple) sugar for <code>getSuccessors(vertex)</code>. 
+     * @param vertex the vertex whose children are to be returned
+     * @return the <code>Collection</code> of children of <code>vertex</code> 
+     * in this tree
+     * @see Graph#getSuccessors(Object)
+     * @see #getChildEdges(Object)
+     */
+    public Collection<V> getChildren(V vertex);
+    
+    /**
+     * Returns the edges connecting <code>vertex</code> to its children 
+     * in this tree.
+     * The children of a vertex are defined as being the successors of
+     * that vertex on the respective (unique) shortest paths from the root to
+     * those vertices.
+     * This is syntactic (maple) sugar for <code>getOutEdges(vertex)</code>. 
+     * @param vertex the vertex whose child edges are to be returned
+     * @return the <code>Collection</code> of edges connecting 
+     * <code>vertex</code> to its children in this tree
+     * @see Graph#getOutEdges(Object)
+     * @see #getChildren(Object)
+     */
+    public Collection<E> getChildEdges(V vertex);
+    
+    /**
+     * Returns the number of children that <code>vertex</code> has in this tree.
+     * The children of a vertex are defined as being the successors of
+     * that vertex on the respective (unique) shortest paths from the root to
+     * those vertices.
+     * This is syntactic (maple) sugar for <code>getSuccessorCount(vertex)</code>. 
+     * @param vertex the vertex whose child edges are to be returned
+     * @return the <code>Collection</code> of edges connecting 
+     * <code>vertex</code> to its children in this tree
+     * @see #getChildEdges(Object)
+     * @see #getChildren(Object)
+     * @see Graph#getSuccessorCount(Object)
+     */
+    public int getChildCount(V vertex);
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/Graph.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/Graph.java
new file mode 100644
index 0000000..ce96edb
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/Graph.java
@@ -0,0 +1,247 @@
+/*
+ * Created on Oct 17, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.Collection;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+
+/**
+ * A graph consisting of a set of vertices of type <code>V</code>
+ * set and a set of edges of type <code>E</code>.  Edges of this
+ * graph type have exactly two endpoints; whether these endpoints 
+ * must be distinct depends on the implementation.
+ * <P>
+ * This interface permits, but does not enforce, any of the following 
+ * common variations of graphs:
+ * <ul>
+ * <li> directed and undirected edges
+ * <li> vertices and edges with attributes (for example, weighted edges)
+ * <li> vertices and edges of different types (for example, bipartite 
+ *      or multimodal graphs)
+ * <li> parallel edges (multiple edges which connect a single set of vertices)
+ * <li> representations as matrices or as adjacency lists or adjacency maps
+ * </ul> 
+ * Extensions or implementations of this interface 
+ * may enforce or disallow any or all of these variations.
+ * 
+ * <p>Definitions (with respect to a given vertex <code>v</code>):
+ * <ul>
+ * <li><b>incoming edge</b> of <code>v</code>: an edge that can be traversed 
+ * from a neighbor of <code>v</code> to reach <code>v</code>
+ * <li><b>outgoing edge</b> of <code>v</code>: an edge that can be traversed
+ * from <code>v</code> to reach some neighbor of <code>v</code> 
+ * <li><b>predecessor</b> of <code>v</code>: a vertex at the other end of an
+ * incoming edge of <code>v</code>
+ * <li><b>successor</b> of <code>v</code>: a vertex at the other end of an 
+ * outgoing edge of <code>v</code>
+ * <li>
+ * </ul> 
+ * 
+ * @author Joshua O'Madadhain
+ */
+public interface Graph<V,E> extends Hypergraph<V,E>
+{
+    /**
+     * Returns a <code>Collection</code> view of the incoming edges incident to <code>vertex</code>
+     * in this graph.
+     * @param vertex    the vertex whose incoming edges are to be returned
+     * @return  a <code>Collection</code> view of the incoming edges incident 
+     * to <code>vertex</code> in this graph
+     */
+    Collection<E> getInEdges(V vertex);
+    
+    /**
+     * Returns a <code>Collection</code> view of the outgoing edges incident to <code>vertex</code>
+     * in this graph.
+     * @param vertex    the vertex whose outgoing edges are to be returned
+     * @return  a <code>Collection</code> view of the outgoing edges incident 
+     * to <code>vertex</code> in this graph
+     */
+    Collection<E> getOutEdges(V vertex);
+
+    /**
+     * Returns a <code>Collection</code> view of the predecessors of <code>vertex</code> 
+     * in this graph.  A predecessor of <code>vertex</code> is defined as a vertex <code>v</code> 
+     * which is connected to 
+     * <code>vertex</code> by an edge <code>e</code>, where <code>e</code> is an outgoing edge of 
+     * <code>v</code> and an incoming edge of <code>vertex</code>.
+     * @param vertex    the vertex whose predecessors are to be returned
+     * @return  a <code>Collection</code> view of the predecessors of 
+     * <code>vertex</code> in this graph
+     */
+    Collection<V> getPredecessors(V vertex);
+    
+    /**
+     * Returns a <code>Collection</code> view of the successors of <code>vertex</code> 
+     * in this graph.  A successor of <code>vertex</code> is defined as a vertex <code>v</code> 
+     * which is connected to 
+     * <code>vertex</code> by an edge <code>e</code>, where <code>e</code> is an incoming edge of 
+     * <code>v</code> and an outgoing edge of <code>vertex</code>.
+     * @param vertex    the vertex whose predecessors are to be returned
+     * @return  a <code>Collection</code> view of the successors of 
+     * <code>vertex</code> in this graph
+     */
+    Collection<V> getSuccessors(V vertex);
+    
+    /**
+     * Returns the number of incoming edges incident to <code>vertex</code>.
+     * Equivalent to <code>getInEdges(vertex).size()</code>.
+     * @param vertex    the vertex whose indegree is to be calculated
+     * @return  the number of incoming edges incident to <code>vertex</code>
+     */
+    int inDegree(V vertex);
+    
+    /**
+     * Returns the number of outgoing edges incident to <code>vertex</code>.
+     * Equivalent to <code>getOutEdges(vertex).size()</code>.
+     * @param vertex    the vertex whose outdegree is to be calculated
+     * @return  the number of outgoing edges incident to <code>vertex</code>
+     */
+    int outDegree(V vertex);
+    
+    /**
+     * Returns <code>true</code> if <code>v1</code> is a predecessor of <code>v2</code> in this graph.
+     * Equivalent to <code>v1.getPredecessors().contains(v2)</code>.
+     * @param v1 the first vertex to be queried
+     * @param v2 the second vertex to be queried
+     * @return <code>true</code> if <code>v1</code> is a predecessor of <code>v2</code>, and false otherwise.
+     */
+    boolean isPredecessor(V v1, V v2);
+    
+    /**
+     * Returns <code>true</code> if <code>v1</code> is a successor of <code>v2</code> in this graph.
+     * Equivalent to <code>v1.getSuccessors().contains(v2)</code>.
+     * @param v1 the first vertex to be queried
+     * @param v2 the second vertex to be queried
+     * @return <code>true</code> if <code>v1</code> is a successor of <code>v2</code>, and false otherwise.
+     */
+    boolean isSuccessor(V v1, V v2);
+
+    /**
+     * Returns the number of predecessors that <code>vertex</code> has in this graph.
+     * Equivalent to <code>vertex.getPredecessors().size()</code>.
+     * @param vertex the vertex whose predecessor count is to be returned
+     * @return  the number of predecessors that <code>vertex</code> has in this graph
+     */
+    int getPredecessorCount(V vertex);
+    
+    /**
+     * Returns the number of successors that <code>vertex</code> has in this graph.
+     * Equivalent to <code>vertex.getSuccessors().size()</code>.
+     * @param vertex the vertex whose successor count is to be returned
+     * @return  the number of successors that <code>vertex</code> has in this graph
+     */
+    int getSuccessorCount(V vertex);
+    
+    /**
+     * If <code>directed_edge</code> is a directed edge in this graph, returns the source; 
+     * otherwise returns <code>null</code>. 
+     * The source of a directed edge <code>d</code> is defined to be the vertex for which  
+     * <code>d</code> is an outgoing edge.
+     * <code>directed_edge</code> is guaranteed to be a directed edge if 
+     * its <code>EdgeType</code> is <code>DIRECTED</code>. 
+     * @param directed_edge the edge whose source is to be returned
+     * @return  the source of <code>directed_edge</code> if it is a directed edge in this graph, or <code>null</code> otherwise
+     */
+    V getSource(E directed_edge);
+
+    /**
+     * If <code>directed_edge</code> is a directed edge in this graph, returns the destination; 
+     * otherwise returns <code>null</code>. 
+     * The destination of a directed edge <code>d</code> is defined to be the vertex 
+     * incident to <code>d</code> for which  
+     * <code>d</code> is an incoming edge.
+     * <code>directed_edge</code> is guaranteed to be a directed edge if 
+     * its <code>EdgeType</code> is <code>DIRECTED</code>.
+     * @param directed_edge the edge whose destination is to be returned
+     * @return  the destination of <code>directed_edge</code> if it is a directed edge in this graph, or <code>null</code> otherwise
+     */
+    V getDest(E directed_edge);
+    
+    /**
+     * Returns <code>true</code> if <code>vertex</code> is the source of <code>edge</code>.
+     * Equivalent to <code>getSource(edge).equals(vertex)</code>.
+     * @param vertex the vertex to be queried
+     * @param edge the edge to be queried
+     * @return <code>true</code> iff <code>vertex</code> is the source of <code>edge</code>
+     */
+    boolean isSource(V vertex, E edge);
+    
+    /**
+     * Returns <code>true</code> if <code>vertex</code> is the destination of <code>edge</code>.
+     * Equivalent to <code>getDest(edge).equals(vertex)</code>.
+     * @param vertex the vertex to be queried
+     * @param edge the edge to be queried
+     * @return <code>true</code> iff <code>vertex</code> is the destination of <code>edge</code>
+     */
+    boolean isDest(V vertex, E edge);
+
+    /**
+     * Adds edge <code>e</code> to this graph such that it connects 
+     * vertex <code>v1</code> to <code>v2</code>.
+     * Equivalent to <code>addEdge(e, new Pair(v1, v2))</code>.
+     * If this graph does not contain <code>v1</code>, <code>v2</code>, 
+     * or both, implementations may choose to either silently add 
+     * the vertices to the graph or throw an <code>IllegalArgumentException</code>.
+     * If this graph assigns edge types to its edges, the edge type of
+     * <code>e</code> will be the default for this graph.
+     * See <code>Hypergraph.addEdge()</code> for a listing of possible reasons
+     * for failure.
+     * @param e the edge to be added
+     * @param v1 the first vertex to be connected
+     * @param v2 the second vertex to be connected
+     * @return <code>true</code> if the add is successful, <code>false</code> otherwise
+     * @see Hypergraph#addEdge(Object, Collection)
+     * @see #addEdge(Object, Object, Object, EdgeType)
+     */
+    boolean addEdge(E e, V v1, V v2);
+    
+    /**
+     * Adds edge <code>e</code> to this graph such that it connects 
+     * vertex <code>v1</code> to <code>v2</code>.
+     * Equivalent to <code>addEdge(e, new Pair(v1, v2))</code>.
+     * If this graph does not contain <code>v1</code>, <code>v2</code>, 
+     * or both, implementations may choose to either silently add 
+     * the vertices to the graph or throw an <code>IllegalArgumentException</code>.
+     * If <code>edgeType</code> is not legal for this graph, this method will
+     * throw <code>IllegalArgumentException</code>.
+     * See <code>Hypergraph.addEdge()</code> for a listing of possible reasons
+     * for failure.
+     * @param e the edge to be added
+     * @param v1 the first vertex to be connected
+     * @param v2 the second vertex to be connected
+     * @param edgeType the type to be assigned to the edge
+     * @return <code>true</code> if the add is successful, <code>false</code> otherwise
+     * @see Hypergraph#addEdge(Object, Collection)
+     * @see #addEdge(Object, Object, Object)
+     */
+    boolean addEdge(E e, V v1, V v2, EdgeType edgeType);
+
+    /**
+     * Returns the endpoints of <code>edge</code> as a <code>Pair</code>.
+     * @param edge the edge whose endpoints are to be returned
+     * @return the endpoints (incident vertices) of <code>edge</code>
+     */
+    Pair<V> getEndpoints(E edge);
+    
+    /**
+     * Returns the vertex at the other end of <code>edge</code> from <code>vertex</code>.
+     * (That is, returns the vertex incident to <code>edge</code> which is not <code>vertex</code>.)
+     * @param vertex the vertex to be queried
+     * @param edge the edge to be queried
+     * @return the vertex at the other end of <code>edge</code> from <code>vertex</code>
+     */
+    V getOpposite(V vertex, E edge); 
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/GraphDecorator.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/GraphDecorator.java
new file mode 100644
index 0000000..d193179
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/GraphDecorator.java
@@ -0,0 +1,332 @@
+package edu.uci.ics.jung.graph;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * An implementation of <code>Graph</code> that delegates its method calls to a
+ * constructor-specified <code>Graph</code> instance.  This is useful for adding
+ * additional behavior (such as synchronization or unmodifiability) to an existing
+ * instance.
+ */
+ at SuppressWarnings("serial")
+public class GraphDecorator<V,E> implements Graph<V,E>, Serializable {
+	
+	protected Graph<V,E> delegate;
+
+    /**
+     * Creates a new instance based on the provided {@code delegate}.
+     * @param delegate the graph to which method calls will be delegated
+     */
+	public GraphDecorator(Graph<V, E> delegate) {
+		this.delegate = delegate;
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#addEdge(java.lang.Object, java.util.Collection)
+	 */
+	public boolean addEdge(E edge, Collection<? extends V> vertices) {
+		return delegate.addEdge(edge, vertices);
+	}
+	
+	/**
+	 * @see Hypergraph#addEdge(Object, Collection, EdgeType)
+	 */
+	public boolean addEdge(E edge, Collection<? extends V> vertices, EdgeType
+		edge_type)
+	{
+		return delegate.addEdge(edge, vertices, edge_type);
+	}
+	
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object, edu.uci.ics.jung.graph.util.EdgeType)
+	 */
+	public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) {
+		return delegate.addEdge(e, v1, v2, edgeType);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object)
+	 */
+	public boolean addEdge(E e, V v1, V v2) {
+		return delegate.addEdge(e, v1, v2);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#addVertex(java.lang.Object)
+	 */
+	public boolean addVertex(V vertex) {
+		return delegate.addVertex(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#isIncident(java.lang.Object, java.lang.Object)
+	 */
+	public boolean isIncident(V vertex, E edge) {
+		return delegate.isIncident(vertex, edge);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#isNeighbor(java.lang.Object, java.lang.Object)
+	 */
+	public boolean isNeighbor(V v1, V v2) {
+		return delegate.isNeighbor(v1, v2);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#degree(java.lang.Object)
+	 */
+	public int degree(V vertex) {
+		return delegate.degree(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#findEdge(java.lang.Object, java.lang.Object)
+	 */
+	public E findEdge(V v1, V v2) {
+		return delegate.findEdge(v1, v2);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#findEdgeSet(java.lang.Object, java.lang.Object)
+	 */
+	public Collection<E> findEdgeSet(V v1, V v2) {
+		return delegate.findEdgeSet(v1, v2);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#getDest(java.lang.Object)
+	 */
+	public V getDest(E directed_edge) {
+		return delegate.getDest(directed_edge);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount()
+	 */
+	public int getEdgeCount() {
+		return delegate.getEdgeCount();
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount(EdgeType)
+	 */
+	public int getEdgeCount(EdgeType edge_type) 
+	{
+	    return delegate.getEdgeCount(edge_type);
+	}
+	
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#getEdges()
+	 */
+	public Collection<E> getEdges() {
+		return delegate.getEdges();
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#getEdges(edu.uci.ics.jung.graph.util.EdgeType)
+	 */
+	public Collection<E> getEdges(EdgeType edgeType) {
+		return delegate.getEdges(edgeType);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#getEdgeType(java.lang.Object)
+	 */
+	public EdgeType getEdgeType(E edge) {
+		return delegate.getEdgeType(edge);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#getDefaultEdgeType()
+	 */
+	public EdgeType getDefaultEdgeType() 
+	{
+		return delegate.getDefaultEdgeType();
+	}
+	
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#getEndpoints(java.lang.Object)
+	 */
+	public Pair<V> getEndpoints(E edge) {
+		return delegate.getEndpoints(edge);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentCount(java.lang.Object)
+	 */
+	public int getIncidentCount(E edge) {
+		return delegate.getIncidentCount(edge);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentEdges(java.lang.Object)
+	 */
+	public Collection<E> getIncidentEdges(V vertex) {
+		return delegate.getIncidentEdges(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentVertices(java.lang.Object)
+	 */
+	public Collection<V> getIncidentVertices(E edge) {
+		return delegate.getIncidentVertices(edge);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#getInEdges(java.lang.Object)
+	 */
+	public Collection<E> getInEdges(V vertex) {
+		return delegate.getInEdges(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#getNeighborCount(java.lang.Object)
+	 */
+	public int getNeighborCount(V vertex) {
+		return delegate.getNeighborCount(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#getNeighbors(java.lang.Object)
+	 */
+	public Collection<V> getNeighbors(V vertex) {
+		return delegate.getNeighbors(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#getOpposite(java.lang.Object, java.lang.Object)
+	 */
+	public V getOpposite(V vertex, E edge) {
+		return delegate.getOpposite(vertex, edge);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#getOutEdges(java.lang.Object)
+	 */
+	public Collection<E> getOutEdges(V vertex) {
+		return delegate.getOutEdges(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#getPredecessorCount(java.lang.Object)
+	 */
+	public int getPredecessorCount(V vertex) {
+		return delegate.getPredecessorCount(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#getPredecessors(java.lang.Object)
+	 */
+	public Collection<V> getPredecessors(V vertex) {
+		return delegate.getPredecessors(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#getSource(java.lang.Object)
+	 */
+	public V getSource(E directed_edge) {
+		return delegate.getSource(directed_edge);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#getSuccessorCount(java.lang.Object)
+	 */
+	public int getSuccessorCount(V vertex) {
+		return delegate.getSuccessorCount(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#getSuccessors(java.lang.Object)
+	 */
+	public Collection<V> getSuccessors(V vertex) {
+		return delegate.getSuccessors(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#getVertexCount()
+	 */
+	public int getVertexCount() {
+		return delegate.getVertexCount();
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#getVertices()
+	 */
+	public Collection<V> getVertices() {
+		return delegate.getVertices();
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#inDegree(java.lang.Object)
+	 */
+	public int inDegree(V vertex) {
+		return delegate.inDegree(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#isDest(java.lang.Object, java.lang.Object)
+	 */
+	public boolean isDest(V vertex, E edge) {
+		return delegate.isDest(vertex, edge);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#isPredecessor(java.lang.Object, java.lang.Object)
+	 */
+	public boolean isPredecessor(V v1, V v2) {
+		return delegate.isPredecessor(v1, v2);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#isSource(java.lang.Object, java.lang.Object)
+	 */
+	public boolean isSource(V vertex, E edge) {
+		return delegate.isSource(vertex, edge);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#isSuccessor(java.lang.Object, java.lang.Object)
+	 */
+	public boolean isSuccessor(V v1, V v2) {
+		return delegate.isSuccessor(v1, v2);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#outDegree(java.lang.Object)
+	 */
+	public int outDegree(V vertex) {
+		return delegate.outDegree(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object)
+	 */
+	public boolean removeEdge(E edge) {
+		return delegate.removeEdge(edge);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object)
+	 */
+	public boolean removeVertex(V vertex) {
+		return delegate.removeVertex(vertex);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#containsEdge(java.lang.Object)
+	 */
+	public boolean containsEdge(E edge) {
+		return delegate.containsEdge(edge);
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#containsVertex(java.lang.Object)
+	 */
+	public boolean containsVertex(V vertex) {
+		return delegate.containsVertex(vertex);
+	}
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/Hypergraph.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/Hypergraph.java
new file mode 100644
index 0000000..441a984
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/Hypergraph.java
@@ -0,0 +1,442 @@
+/*
+ * Created on Oct 17, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.Collection;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+/**
+ * A hypergraph, consisting of a set of vertices of type <code>V</code>
+ * and a set of hyperedges of type <code>E</code> which connect the vertices.  
+ * This is the base interface for all JUNG graph types.
+ * <P>
+ * This interface permits, but does not enforce, any of the following 
+ * common variations of graphs:
+ * <ul>
+ * <li>hyperedges (edges which connect a set of vertices of any size)
+ * <li>edges (these have have exactly two endpoints, which may or may not be distinct)
+ * <li>self-loops (edges which connect exactly one vertex)
+ * <li>directed and undirected edges
+ * <li>vertices and edges with attributes (for example, weighted edges)
+ * <li>vertices and edges with different constraints or properties (for example, bipartite 
+ *      or multimodal graphs)
+ * <li>parallel edges (multiple edges which connect a single set of vertices)
+ * <li>internal representations as matrices or as adjacency lists or adjacency maps
+ * </ul> 
+ * Extensions or implementations of this interface 
+ * may enforce or disallow any or all of these variations.
+ * <p><b>Notes</b>:
+ * <ul>
+ * <li> The collections returned by <code>Hypergraph</code> instances
+ * should be treated in general as if read-only.  While they are not contractually 
+ * guaranteed (or required) to be immutable,
+ * this interface does not define the outcome if they are mutated.
+ * Mutations should be done via <code>{add,remove}{Edge,Vertex}</code>, or
+ * in the constructor.
+ * <li> 
+ * </ul>
+ * 
+ * @author Joshua O'Madadhain
+ */
+public interface Hypergraph<V, E>
+{
+    /**
+     * Returns a view of all edges in this graph. In general, this
+     * obeys the <code>Collection</code> contract, and therefore makes no guarantees 
+     * about the ordering of the vertices within the set.
+     * @return a <code>Collection</code> view of all edges in this graph
+     */
+    Collection<E> getEdges();
+    
+    /**
+     * Returns a view of all vertices in this graph. In general, this
+     * obeys the <code>Collection</code> contract, and therefore makes no guarantees 
+     * about the ordering of the vertices within the set.
+     * @return a <code>Collection</code> view of all vertices in this graph
+     */
+    Collection<V> getVertices();
+    
+    /**
+     * Returns true if this graph's vertex collection contains <code>vertex</code>.
+     * Equivalent to <code>getVertices().contains(vertex)</code>.
+     * @param vertex the vertex whose presence is being queried
+     * @return true iff this graph contains a vertex <code>vertex</code>
+     */
+    boolean containsVertex(V vertex);
+    
+    /**
+     * Returns true if this graph's edge collection contains <code>edge</code>.
+     * Equivalent to <code>getEdges().contains(edge)</code>.
+     * @param edge the edge whose presence is being queried
+     * @return true iff this graph contains an edge <code>edge</code>
+     */
+    boolean containsEdge(E edge);
+    
+    /**
+     * Returns the number of edges in this graph.
+     * @return the number of edges in this graph
+     */
+    int getEdgeCount();
+    
+    /**
+     * Returns the number of vertices in this graph.
+     * @return the number of vertices in this graph
+     */
+    int getVertexCount();
+
+    /**
+     * Returns the collection of vertices which are connected to <code>vertex</code>
+     * via any edges in this graph.
+     * If <code>vertex</code> is connected to itself with a self-loop, then 
+     * it will be included in the collection returned.
+     * 
+     * @param vertex the vertex whose neighbors are to be returned
+     * @return  the collection of vertices which are connected to <code>vertex</code>, 
+     * or <code>null</code> if <code>vertex</code> is not present
+     */
+    Collection<V> getNeighbors(V vertex);
+    
+    /**
+     * Returns the collection of edges in this graph which are connected to <code>vertex</code>.
+     * 
+     * @param vertex the vertex whose incident edges are to be returned
+     * @return  the collection of edges which are connected to <code>vertex</code>, 
+     * or <code>null</code> if <code>vertex</code> is not present
+     */
+    Collection<E> getIncidentEdges(V vertex);
+    
+    /**
+     * Returns the collection of vertices in this graph which are connected to <code>edge</code>.
+     * Note that for some graph types there are guarantees about the size of this collection
+     * (i.e., some graphs contain edges that have exactly two endpoints, which may or may 
+     * not be distinct).  Implementations for those graph types may provide alternate methods 
+     * that provide more convenient access to the vertices.
+     * 
+     * @param edge the edge whose incident vertices are to be returned
+     * @return  the collection of vertices which are connected to <code>edge</code>, 
+     * or <code>null</code> if <code>edge</code> is not present
+     */
+    Collection<V> getIncidentVertices(E edge);
+    
+    /**
+     * Returns an edge that connects this vertex to <code>v</code>.
+     * If this edge is not uniquely
+     * defined (that is, if the graph contains more than one edge connecting 
+     * <code>v1</code> to <code>v2</code>), any of these edges 
+     * may be returned.  <code>findEdgeSet(v1, v2)</code> may be 
+     * used to return all such edges.
+     * Returns null if either of the following is true:
+     * <ul>
+     * <li><code>v2</code> is not connected to <code>v1</code>
+     * <li>either <code>v1</code> or <code>v2</code> are not present in this graph
+     * </ul> 
+     * <p><b>Note</b>: for purposes of this method, <code>v1</code> is only considered to be connected to
+     * <code>v2</code> via a given <i>directed</i> edge <code>e</code> if
+     * <code>v1 == e.getSource() && v2 == e.getDest()</code> evaluates to <code>true</code>.
+     * (<code>v1</code> and <code>v2</code> are connected by an undirected edge <code>u</code> if 
+     * <code>u</code> is incident to both <code>v1</code> and <code>v2</code>.)
+     * 
+     * @param v1 the first endpoint of the returned edge
+     * @param v2 the second endpoint of the returned edge
+     * @return  an edge that connects <code>v1</code> to <code>v2</code>, 
+     * or <code>null</code> if no such edge exists (or either vertex is not present)
+     * @see Hypergraph#findEdgeSet(Object, Object) 
+     */
+    E findEdge(V v1, V v2);
+    
+    /**
+     * Returns all edges that connects this vertex to <code>v</code>.
+     * If this edge is not uniquely
+     * defined (that is, if the graph contains more than one edge connecting 
+     * <code>v1</code> to <code>v2</code>), any of these edges 
+     * may be returned.  <code>findEdgeSet(v1, v2)</code> may be 
+     * used to return all such edges.
+     * Returns null if <code>v2</code> is not connected to <code>v1</code>.
+     * <br>Returns an empty collection if either <code>v1</code> or <code>v2</code>
+     * are not present in this graph.
+     *  
+     * <p><b>Note</b>: for purposes of this method, <code>v1</code> is only considered to be connected to
+     * <code>v2</code> via a given <i>directed</i> edge <code>d</code> if
+     * <code>v1 == d.getSource() && v2 == d.getDest()</code> evaluates to <code>true</code>.
+     * (<code>v1</code> and <code>v2</code> are connected by an undirected edge <code>u</code> if 
+     * <code>u</code> is incident to both <code>v1</code> and <code>v2</code>.)
+     * 
+     * @param v1 the first endpoint of the returned edge set
+     * @param v2 the second endpoint of the returned edge set
+     * @return  a collection containing all edges that connect <code>v1</code> to <code>v2</code>, 
+     * or <code>null</code> if either vertex is not present
+     * @see Hypergraph#findEdge(Object, Object) 
+     */
+    Collection<E> findEdgeSet(V v1, V v2);
+    
+    /**
+     * Adds <code>vertex</code> to this graph.
+     * Fails if <code>vertex</code> is null or already in the graph.
+     * 
+     * @param vertex    the vertex to add
+     * @return <code>true</code> if the add is successful, and <code>false</code> otherwise
+     * @throws IllegalArgumentException if <code>vertex</code> is <code>null</code>
+     */
+    boolean addVertex(V vertex);
+    
+    /**
+     * Adds <code>edge</code> to this graph.
+     * Fails under the following circumstances:
+     * <ul>
+     * <li><code>edge</code> is already an element of the graph 
+     * <li>either <code>edge</code> or <code>vertices</code> is <code>null</code>
+     * <li><code>vertices</code> has the wrong number of vertices for the graph type
+     * <li><code>vertices</code> are already connected by another edge in this graph,
+     * and this graph does not accept parallel edges
+     * </ul>
+     * 
+     * @param edge the edge to add
+     * @param vertices the vertices to which the edge will be connected
+     * @return <code>true</code> if the add is successful, and <code>false</code> otherwise
+     * @throws IllegalArgumentException if <code>edge</code> or <code>vertices</code> is null, 
+     * or if a different vertex set in this graph is already connected by <code>edge</code>, 
+     * or if <code>vertices</code> are not a legal vertex set for <code>edge</code> 
+     */
+    boolean addEdge(E edge, Collection<? extends V> vertices);
+
+    /**
+     * Adds <code>edge</code> to this graph with type <code>edge_type</code>.
+     * Fails under the following circumstances:
+     * <ul>
+     * <li><code>edge</code> is already an element of the graph 
+     * <li>either <code>edge</code> or <code>vertices</code> is <code>null</code>
+     * <li><code>vertices</code> has the wrong number of vertices for the graph type
+     * <li><code>vertices</code> are already connected by another edge in this graph,
+     * and this graph does not accept parallel edges
+     * <li><code>edge_type</code> is not legal for this graph
+     * </ul>
+     * 
+     * @param edge edge to add to this graph
+     * @param vertices vertices which are to be connected by this edge
+     * @param edge_type type of edge to add
+     * @return <code>true</code> if the add is successful, and <code>false</code> otherwise
+     * @throws IllegalArgumentException if <code>edge</code> or <code>vertices</code> is null, 
+     * or if a different vertex set in this graph is already connected by <code>edge</code>, 
+     * or if <code>vertices</code> are not a legal vertex set for <code>edge</code> 
+     */
+    boolean addEdge(E edge, Collection<? extends V> vertices, EdgeType 
+    		edge_type);
+    
+    /**
+     * Removes <code>vertex</code> from this graph.
+     * As a side effect, removes any edges <code>e</code> incident to <code>vertex</code> if the 
+     * removal of <code>vertex</code> would cause <code>e</code> to be incident to an illegal
+     * number of vertices.  (Thus, for example, incident hyperedges are not removed, but 
+     * incident edges--which must be connected to a vertex at both endpoints--are removed.) 
+     * 
+     * <p>Fails under the following circumstances:
+     * <ul>
+     * <li><code>vertex</code> is not an element of this graph
+     * <li><code>vertex</code> is <code>null</code>
+     * </ul>
+     * 
+     * @param vertex the vertex to remove
+     * @return <code>true</code> if the removal is successful, <code>false</code> otherwise
+     */
+    boolean removeVertex(V vertex);
+
+    /**
+     * Removes <code>edge</code> from this graph.
+     * Fails if <code>edge</code> is null, or is otherwise not an element of this graph.
+     * 
+     * @param edge the edge to remove
+     * @return <code>true</code> if the removal is successful, <code>false</code> otherwise
+     */
+    boolean removeEdge(E edge);
+
+    
+    /**
+     * Returns <code>true</code> if <code>v1</code> and <code>v2</code> share an incident edge.
+     * Equivalent to <code>getNeighbors(v1).contains(v2)</code>.
+     * 
+     * @param v1 the first vertex to test
+     * @param v2 the second vertex to test
+     * @return <code>true</code> if <code>v1</code> and <code>v2</code> share an incident edge
+     */
+    boolean isNeighbor(V v1, V v2);
+
+    /**
+     * Returns <code>true</code> if <code>vertex</code> and <code>edge</code> 
+     * are incident to each other.
+     * Equivalent to <code>getIncidentEdges(vertex).contains(edge)</code> and to
+     * <code>getIncidentVertices(edge).contains(vertex)</code>.
+     * @param vertex vertex to test
+     * @param edge edge to test
+     * @return <code>true</code> if <code>vertex</code> and <code>edge</code> 
+     * are incident to each other
+     */
+    boolean isIncident(V vertex, E edge);
+    
+    /**
+     * Returns the number of edges incident to <code>vertex</code>.  
+     * Special cases of interest:
+     * <ul>
+     * <li> Incident self-loops are counted once.
+     * <li> If there is only one edge that connects this vertex to
+     * each of its neighbors (and vice versa), then the value returned 
+     * will also be equal to the number of neighbors that this vertex has
+     * (that is, the output of <code>getNeighborCount</code>).
+     * <li> If the graph is directed, then the value returned will be 
+     * the sum of this vertex's indegree (the number of edges whose 
+     * destination is this vertex) and its outdegree (the number
+     * of edges whose source is this vertex), minus the number of
+     * incident self-loops (to avoid double-counting).
+     * </ul>
+     * <p>Equivalent to <code>getIncidentEdges(vertex).size()</code>.
+     * 
+     * @param vertex the vertex whose degree is to be returned
+     * @return the degree of this node
+     * @see Hypergraph#getNeighborCount(Object)
+     */
+    int degree(V vertex);
+
+    /**
+     * Returns the number of vertices that are adjacent to <code>vertex</code>
+     * (that is, the number of vertices that are incident to edges in <code>vertex</code>'s
+     * incident edge set).
+     * 
+     * <p>Equivalent to <code>getNeighbors(vertex).size()</code>.
+     * @param vertex the vertex whose neighbor count is to be returned
+     * @return the number of neighboring vertices
+     */
+    int getNeighborCount(V vertex);
+    
+    /**
+     * Returns the number of vertices that are incident to <code>edge</code>.
+     * For hyperedges, this can be any nonnegative integer; for edges this
+     * must be 2 (or 1 if self-loops are permitted). 
+     * 
+     * <p>Equivalent to <code>getIncidentVertices(edge).size()</code>.
+     * @param edge the edge whose incident vertex count is to be returned
+     * @return the number of vertices that are incident to <code>edge</code>.
+     */
+    int getIncidentCount(E edge);
+    
+    /**
+     * Returns the edge type of <code>edge</code> in this graph.
+     * @param edge the edge whose type is to be returned
+     * @return the <code>EdgeType</code> of <code>edge</code>, or <code>null</code> if <code>edge</code> has no defined type
+     */
+    EdgeType getEdgeType(E edge); 
+    
+    /**
+     * Returns the default edge type for this graph.
+     * 
+     * @return the default edge type for this graph
+     */
+    EdgeType getDefaultEdgeType();
+    
+    /**
+     * Returns the collection of edges in this graph which are of type <code>edge_type</code>.
+     * @param edge_type the type of edges to be returned
+     * @return the collection of edges which are of type <code>edge_type</code>, or
+     * <code>null</code> if the graph does not accept edges of this type
+     * @see EdgeType
+     */
+    Collection<E> getEdges(EdgeType edge_type);
+    
+    /**
+     * Returns the number of edges of type <code>edge_type</code> in this graph.
+     * @param edge_type the type of edge for which the count is to be returned
+     * @return the number of edges of type <code>edge_type</code> in this graph
+     */
+    int getEdgeCount(EdgeType edge_type);
+    
+    /**
+     * Returns a <code>Collection</code> view of the incoming edges incident to <code>vertex</code>
+     * in this graph.
+     * @param vertex    the vertex whose incoming edges are to be returned
+     * @return  a <code>Collection</code> view of the incoming edges incident 
+     * to <code>vertex</code> in this graph
+     */
+    Collection<E> getInEdges(V vertex);
+    
+    /**
+     * Returns a <code>Collection</code> view of the outgoing edges incident to <code>vertex</code>
+     * in this graph.
+     * @param vertex    the vertex whose outgoing edges are to be returned
+     * @return  a <code>Collection</code> view of the outgoing edges incident 
+     * to <code>vertex</code> in this graph
+     */
+    Collection<E> getOutEdges(V vertex);
+    
+    /**
+     * Returns the number of incoming edges incident to <code>vertex</code>.
+     * Equivalent to <code>getInEdges(vertex).size()</code>.
+     * @param vertex    the vertex whose indegree is to be calculated
+     * @return  the number of incoming edges incident to <code>vertex</code>
+     */
+    int inDegree(V vertex);
+    
+    /**
+     * Returns the number of outgoing edges incident to <code>vertex</code>.
+     * Equivalent to <code>getOutEdges(vertex).size()</code>.
+     * @param vertex    the vertex whose outdegree is to be calculated
+     * @return  the number of outgoing edges incident to <code>vertex</code>
+     */
+    int outDegree(V vertex);
+    
+    /**
+     * If <code>directed_edge</code> is a directed edge in this graph, returns the source; 
+     * otherwise returns <code>null</code>. 
+     * The source of a directed edge <code>d</code> is defined to be the vertex for which  
+     * <code>d</code> is an outgoing edge.
+     * <code>directed_edge</code> is guaranteed to be a directed edge if 
+     * its <code>EdgeType</code> is <code>DIRECTED</code>. 
+     * @param directed_edge the edge whose source is to be returned
+     * @return  the source of <code>directed_edge</code> if it is a directed edge in this graph, or <code>null</code> otherwise
+     */
+    V getSource(E directed_edge);
+
+    /**
+     * If <code>directed_edge</code> is a directed edge in this graph, returns the destination; 
+     * otherwise returns <code>null</code>. 
+     * The destination of a directed edge <code>d</code> is defined to be the vertex 
+     * incident to <code>d</code> for which  
+     * <code>d</code> is an incoming edge.
+     * <code>directed_edge</code> is guaranteed to be a directed edge if 
+     * its <code>EdgeType</code> is <code>DIRECTED</code>. 
+     * @param directed_edge the edge whose destination is to be returned
+     * @return  the destination of <code>directed_edge</code> if it is a directed edge in this graph, or <code>null</code> otherwise
+     */
+    V getDest(E directed_edge);
+
+    /**
+     * Returns a <code>Collection</code> view of the predecessors of <code>vertex</code> 
+     * in this graph.  A predecessor of <code>vertex</code> is defined as a vertex <code>v</code> 
+     * which is connected to 
+     * <code>vertex</code> by an edge <code>e</code>, where <code>e</code> is an outgoing edge of 
+     * <code>v</code> and an incoming edge of <code>vertex</code>.
+     * @param vertex    the vertex whose predecessors are to be returned
+     * @return  a <code>Collection</code> view of the predecessors of 
+     * <code>vertex</code> in this graph
+     */
+    Collection<V> getPredecessors(V vertex);
+    
+    /**
+     * Returns a <code>Collection</code> view of the successors of <code>vertex</code> 
+     * in this graph.  A successor of <code>vertex</code> is defined as a vertex <code>v</code> 
+     * which is connected to 
+     * <code>vertex</code> by an edge <code>e</code>, where <code>e</code> is an incoming edge of 
+     * <code>v</code> and an outgoing edge of <code>vertex</code>.
+     * @param vertex    the vertex whose predecessors are to be returned
+     * @return  a <code>Collection</code> view of the successors of 
+     * <code>vertex</code> in this graph
+     */
+    Collection<V> getSuccessors(V vertex);
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/KPartiteGraph.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/KPartiteGraph.java
new file mode 100644
index 0000000..e6f2cc2
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/KPartiteGraph.java
@@ -0,0 +1,40 @@
+/*
+ * Created on May 24, 2008
+ *
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.Collection;
+
+import com.google.common.base.Predicate;
+
+/**
+ * An interface for graphs whose vertices are each members of one of 2 or more 
+ * disjoint sets (partitions), and whose edges connect only vertices in distinct
+ * partitions.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public interface KPartiteGraph<V,E> extends Graph<V,E>
+{
+    /**
+     * Returns all vertices which satisfy the specified <code>partition</code> predicate.
+     * @param partition <code>Predicate</code> which defines a partition
+     * @return all vertices satisfying <code>partition</code>
+     */
+    public Collection<V> getVertices(Predicate<V> partition);
+
+    /**
+     * Returns the set of <code>Predicate</code> instances which define this graph's partitions.
+     * @return the set of <code>Predicate</code> instances which define this graph's partitions
+     */
+    public Collection<Predicate<V>> getPartitions();
+
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/MultiGraph.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/MultiGraph.java
new file mode 100644
index 0000000..3e66e44
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/MultiGraph.java
@@ -0,0 +1,20 @@
+/*
+ * Created on Aug 31, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+/**
+ * A tagging interface which indicates that the implementing graph accepts
+ * parallel edges.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public interface MultiGraph<V, E> {}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/ObservableGraph.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/ObservableGraph.java
new file mode 100644
index 0000000..179f8e9
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/ObservableGraph.java
@@ -0,0 +1,142 @@
+package edu.uci.ics.jung.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import edu.uci.ics.jung.graph.event.GraphEvent;
+import edu.uci.ics.jung.graph.event.GraphEventListener;
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+/**
+ * A decorator class for graphs which generates events 
+ * 
+ * @author Joshua O'Madadhain
+ */
+ at SuppressWarnings("serial")
+public class ObservableGraph<V,E> extends GraphDecorator<V,E> {
+
+	List<GraphEventListener<V,E>> listenerList = 
+		Collections.synchronizedList(new LinkedList<GraphEventListener<V,E>>());
+
+    /**
+     * Creates a new instance based on the provided {@code delegate}.
+     * 
+     * @param delegate the graph on which this class operates
+     */
+	public ObservableGraph(Graph<V, E> delegate) {
+		super(delegate);
+	}
+	
+	/**
+	 * Adds {@code l} as a listener to this graph.
+	 * 
+	 * @param l the listener to add
+	 */
+	public void addGraphEventListener(GraphEventListener<V,E> l) {
+		listenerList.add(l);
+	}
+
+    /**
+     * Removes {@code l} as a listener to this graph.
+     * 
+	 * @param l the listener to remove
+     */
+	public void removeGraphEventListener(GraphEventListener<V,E> l) {
+		listenerList.remove(l);
+	}
+
+	protected void fireGraphEvent(GraphEvent<V,E> evt) {
+		for(GraphEventListener<V,E> listener : listenerList) {
+			listener.handleGraphEvent(evt);
+		 }
+	 }
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#addEdge(java.lang.Object, java.util.Collection)
+	 */
+	@Override
+	public boolean addEdge(E edge, Collection<? extends V> vertices) {
+		boolean state = super.addEdge(edge, vertices);
+		if(state) {
+			GraphEvent<V,E> evt = new GraphEvent.Edge<V,E>(delegate, GraphEvent.Type.EDGE_ADDED, edge);
+			fireGraphEvent(evt);
+		}
+		return state;
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object, edu.uci.ics.jung.graph.util.EdgeType)
+	 */
+	@Override
+  public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) {
+		boolean state = super.addEdge(e, v1, v2, edgeType);
+		if(state) {
+			GraphEvent<V,E> evt = new GraphEvent.Edge<V,E>(delegate, GraphEvent.Type.EDGE_ADDED, e);
+			fireGraphEvent(evt);
+		}
+		return state;
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object)
+	 */
+	@Override
+  public boolean addEdge(E e, V v1, V v2) {
+		boolean state = super.addEdge(e, v1, v2);
+		if(state) {
+			GraphEvent<V,E> evt = new GraphEvent.Edge<V,E>(delegate, GraphEvent.Type.EDGE_ADDED, e);
+			fireGraphEvent(evt);
+		}
+		return state;
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#addVertex(java.lang.Object)
+	 */
+	@Override
+  public boolean addVertex(V vertex) {
+		boolean state = super.addVertex(vertex);
+		if(state) {
+			GraphEvent<V,E> evt = new GraphEvent.Vertex<V,E>(delegate, GraphEvent.Type.VERTEX_ADDED, vertex);
+			fireGraphEvent(evt);
+		}
+		return state;
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object)
+	 */
+	@Override
+  public boolean removeEdge(E edge) {
+		boolean state = delegate.removeEdge(edge);
+		if(state) {
+			GraphEvent<V,E> evt = new GraphEvent.Edge<V,E>(delegate, GraphEvent.Type.EDGE_REMOVED, edge);
+			fireGraphEvent(evt);
+		}
+		return state;
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object)
+	 */
+	@Override
+	public boolean removeVertex(V vertex) {
+		// remove all incident edges first, so that the appropriate events will
+		// be fired (otherwise they'll be removed inside {@code delegate.removeVertex}
+		// and the events will not be fired)
+		Collection<E> incident_edges = new ArrayList<E>(delegate.getIncidentEdges(vertex));
+		for (E e : incident_edges) 
+			this.removeEdge(e);
+		
+		boolean state = delegate.removeVertex(vertex);
+		if(state) {
+			GraphEvent<V,E> evt = new GraphEvent.Vertex<V,E>(delegate, GraphEvent.Type.VERTEX_REMOVED, vertex);
+			fireGraphEvent(evt);
+		}
+		return state;
+	}
+
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/Tree.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/Tree.java
new file mode 100644
index 0000000..0b38b55
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/Tree.java
@@ -0,0 +1,52 @@
+/*
+ * Created on Feb 3, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+
+/**
+ * A subtype of <code>Graph</code> which is a (directed, rooted) tree.
+ * What we refer to as a "tree" here is actually (in the terminology of graph theory) a
+ * rooted tree.  (That is, there is a designated single vertex--the <i>root</i>--from which we measure
+ * the shortest path to each vertex, which we call its <i>depth</i>; the maximum over all such 
+ * depths is the tree's <i>height</i>.  Note that for a tree, there is exactly
+ * one unique path from the root to any vertex.)
+ * 
+ * @author Joshua O'Madadhain
+ */
+public interface Tree<V,E> extends Forest<V,E>
+{
+    /**
+     * Returns the (unweighted) distance of <code>vertex</code> 
+     * from the root of this tree.
+     * @param vertex    the vertex whose depth is to be returned.
+     * @return the length of the shortest unweighted path 
+     * from <code>vertex</code> to the root of this tree
+     * @see #getHeight()
+     */
+    public int getDepth(V vertex);
+    
+    /**
+     * Returns the maximum depth in this tree.
+     * @return the maximum depth in this tree
+     * @see #getDepth(Object)
+     */
+    public int getHeight();
+    
+    /**
+     * Returns the root of this tree.
+     * The root is defined to be the vertex (designated either at the tree's
+     * creation time, or as the first vertex to be added) with respect to which 
+     * vertex depth is measured.
+     * @return the root of this tree
+     */
+    public V getRoot();
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/UndirectedGraph.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/UndirectedGraph.java
new file mode 100644
index 0000000..0905ac9
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/UndirectedGraph.java
@@ -0,0 +1,18 @@
+/*
+ * Created on Oct 17, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+/**
+ * A tagging interface for extensions of <code>Graph</code> that 
+ * accept only undirected edges.
+ */
+public interface UndirectedGraph<V,E> extends Graph<V,E> {}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/event/GraphEvent.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/event/GraphEvent.java
new file mode 100644
index 0000000..23cb695
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/event/GraphEvent.java
@@ -0,0 +1,117 @@
+package edu.uci.ics.jung.graph.event;
+
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * 
+ * 
+ * @author tom nelson
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+public abstract class GraphEvent<V,E> {
+	
+	protected Graph<V,E> source;
+	protected Type type;
+
+	/**
+	 * Creates an instance with the specified {@code source} graph and {@code Type}
+	 * (vertex/edge addition/removal).
+	 * 
+	 * @param source the graph whose event this is
+	 * @param type the type of event this is
+	 */
+	public GraphEvent(Graph<V, E> source, Type type) {
+		this.source = source;
+		this.type = type;
+	}
+	
+	/**
+	 * Types of graph events.
+	 */
+	public static enum Type {
+		VERTEX_ADDED,
+		VERTEX_REMOVED,
+		EDGE_ADDED,
+		EDGE_REMOVED
+	}
+	
+    /**
+     * An event type pertaining to graph vertices.
+     */
+	public static class Vertex<V,E> extends GraphEvent<V,E> {
+		protected V vertex;
+		
+		/**
+		 * Creates a graph event for the specified graph, vertex, and type.
+		 * 
+         * @param source the graph whose event this is
+         * @param type the type of event this is
+         * @param vertex the vertex involved in this event
+		 */
+		public Vertex(Graph<V,E> source, Type type, V vertex) {
+			super(source,type);
+			this.vertex = vertex;
+		}
+		
+		/**
+		 * @return the vertex associated with this event
+		 */
+		public V getVertex() {
+			return vertex;
+		}
+		
+		@Override
+	    public String toString() {
+			return "GraphEvent type:"+type+" for "+vertex;
+		}
+		
+	}
+	
+	/**
+	 * An event type pertaining to graph edges.
+	 */
+	public static class Edge<V,E> extends GraphEvent<V,E> {
+		protected E edge;
+		
+        /**
+         * Creates a graph event for the specified graph, edge, and type.
+         * 
+         * @param source the graph whose event this is
+         * @param type the type of event this is
+         * @param edge the edge involved in this event
+         */
+		public Edge(Graph<V,E> source, Type type, E edge) {
+			super(source,type);
+			this.edge = edge;
+		}
+		
+		/**
+		 * @return the edge associated with this event.
+		 */
+		public E getEdge() {
+			return edge;
+		}
+		
+		@Override
+    	public String toString() {
+			return "GraphEvent type:"+type+" for "+edge;
+		}
+		
+	}
+	
+	/**
+	 * @return the source
+	 */
+	public Graph<V, E> getSource() {
+		return source;
+	}
+	
+	/**
+	 * @return the type
+	 */
+	public Type getType() {
+		return type;
+	}
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/event/GraphEventListener.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/event/GraphEventListener.java
new file mode 100644
index 0000000..6049f13
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/event/GraphEventListener.java
@@ -0,0 +1,18 @@
+package edu.uci.ics.jung.graph.event;
+
+import java.util.EventListener;
+
+/**
+ * An interface for classes that listen for graph events.
+ */
+public interface GraphEventListener<V,E> extends EventListener 
+{
+	/**
+	 * Method called by the process generating a graph event to which
+	 * this instance is listening.  The implementor of this interface
+	 * is responsible for deciding what behavior is appropriate.
+	 * 
+	 * @param evt the graph event to be handled
+	 */
+	void handleGraphEvent(GraphEvent<V,E> evt);
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/event/package.html b/jung-api/src/main/java/edu/uci/ics/jung/graph/event/package.html
new file mode 100644
index 0000000..df4912c
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/event/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Support for generating events in response to graph actions, especially mutations.
+
+</body>
+</html>
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/package.html b/jung-api/src/main/java/edu/uci/ics/jung/graph/package.html
new file mode 100644
index 0000000..8aa3bf6
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/package.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Interfaces for the JUNG graph types, and some representative implementations.
+
+<P>A graph consists of a set of vertices set and a set of edges which connect the
+vertices.  The base interface is <code>Hypergraph</code>, which defines the most
+general type of graph; other interfaces (<code>Graph</code>, <code>DirectedGraph</code>, etc.)
+define more restrictive graph types.
+<p>Vertex and edge types are specified at compile type using Java 1.5 generics.  
+
+<p>Types of graphs which are supported include (but are not limited to)
+<ul>
+<li>edges (these have have exactly two endpoints, which may or may not be distinct)
+<li>self-loops (edges which connect exactly one vertex)
+<li> directed and undirected edges
+<li> vertices and edges with attributes (for example, weighted edges)
+<li> vertices and edges with different constraints or properties (examples: trees, bipartite 
+     graphs, or multimodal)
+<li> parallel edges (multiple edges which connect a single set of vertices)
+<li> internal representations as matrices or as adjacency lists or adjacency maps
+<li>internal representations that order their elements according to insertion time,
+natural ordering, or a specified <code>Comparator</code>
+</ul> 
+Extensions or implementations of this interface 
+may enforce or disallow any or all of these variations.
+<p><b>Notes</b>:
+<ul>
+<li> The collections returned by graph instances
+should be treated in general as if read-only.  While they are not contractually 
+guaranteed (or required) to be immutable,
+these interfaces do not define the outcome if they are mutated.
+Mutations should be done via <code>{add,remove}{Edge,Vertex}</code>, or
+in the constructor.
+<li> "Wrapper" graphs are available through <code>GraphDecorator</code>; these are useful
+if you want to create a graph implementation that uses another implementation to do the work,
+and adds some extra behavior.  (One example: <code>ObservableGraph</code>, which notifies 
+registered listeners when graph mutations occur.)
+</ul>
+</body>
+</html>
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/util/Context.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/Context.java
new file mode 100644
index 0000000..e1c65bf
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/Context.java
@@ -0,0 +1,50 @@
+package edu.uci.ics.jung.graph.util;
+
+/**
+ * A class that is used to link together a graph element and a specific graph.
+ * Provides appropriate implementations of <code>hashCode</code> and <code>equals</code>.
+ */
+public class Context<G,E> 
+{
+	@SuppressWarnings("rawtypes")
+	private static Context instance = new Context();
+	
+	/**
+	 * The graph element which defines this context.
+	 */
+	public G graph;
+
+	/**
+	 * The edge element which defines this context.
+	 */
+	public E element;
+	
+	/**
+	 * @param <G> the graph type
+	 * @param <E> the element type
+	 * @param graph the graph for which the instance is created
+	 * @param element the element for which the instance is created
+	 * @return an instance of this type for the specified graph and element
+	 */
+	@SuppressWarnings("unchecked")
+	public static <G,E> Context<G,E> getInstance(G graph, E element) {
+		instance.graph = graph;
+		instance.element = element;
+		return instance;
+	}
+	
+	@Override
+	public int hashCode() {
+		return graph.hashCode() ^ element.hashCode();
+	}
+	
+	@SuppressWarnings("rawtypes")
+	@Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Context))
+            return false;
+        Context context = (Context) o;
+        return context.graph.equals(graph) && context.element.equals(element);
+    }
+}
+
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/util/DefaultParallelEdgeIndexFunction.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/DefaultParallelEdgeIndexFunction.java
new file mode 100644
index 0000000..7ea5473
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/DefaultParallelEdgeIndexFunction.java
@@ -0,0 +1,148 @@
+/*
+ * Created on Sep 24, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph.util;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * A class which creates and maintains indices for parallel edges.
+ * Parallel edges are defined here to be the collection of edges 
+ * that are returned by <code>v.findEdgeSet(w)</code> for some 
+ * <code>v</code> and <code>w</code>.
+ * 
+ * <p>At this time, users are responsible for resetting the indices 
+ * (by calling <code>reset()</code>) if changes to the
+ * graph make it appropriate.
+ * 
+ * @author Joshua O'Madadhain
+ * @author Tom Nelson
+ *
+ */
+public class DefaultParallelEdgeIndexFunction<V,E> implements EdgeIndexFunction<V,E>
+{
+    protected Map<Context<Graph<V,E>,E>, Integer> edge_index = new HashMap<Context<Graph<V,E>,E>, Integer>();
+    
+    private DefaultParallelEdgeIndexFunction() {
+    }
+    
+    /**
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @return an instance of this class
+     */
+    public static <V,E> DefaultParallelEdgeIndexFunction<V,E> getInstance() {
+        return new DefaultParallelEdgeIndexFunction<V,E>();
+    }
+    
+    /**
+     * Returns the index for <code>e</code> in <code>graph</code>.
+     * Calculates the indices for <code>e</code> and for all edges parallel
+     * to <code>e</code>, if they are not already assigned.
+     */
+    public int getIndex(Graph<V, E> graph, E e)
+    {
+    	checkNotNull(graph, "graph must not be null");
+    	checkNotNull(e, "'e' must not be null");
+        Integer index = edge_index.get(Context.<Graph<V,E>,E>getInstance(graph,e));
+        	//edge_index.get(e);
+        if(index == null) {
+        	Pair<V> endpoints = graph.getEndpoints(e);
+        	V u = endpoints.getFirst();
+        	V v = endpoints.getSecond();
+        	if(u.equals(v)) {
+        		index = getIndex(graph, e, v);
+        	} else {
+        		index = getIndex(graph, e, u, v);
+        	}
+        }
+        return index.intValue();
+    }
+
+    protected int getIndex(Graph<V,E> graph, E e, V v, V u) {
+    	Collection<E> commonEdgeSet = new HashSet<E>(graph.getIncidentEdges(u));
+    	commonEdgeSet.retainAll(graph.getIncidentEdges(v));
+    	for(Iterator<E> iterator=commonEdgeSet.iterator(); iterator.hasNext(); ) {
+    		E edge = iterator.next();
+    		Pair<V> ep = graph.getEndpoints(edge);
+    		V first = ep.getFirst();
+    		V second = ep.getSecond();
+    		// remove loops
+    		if(first.equals(second) == true) {
+    			iterator.remove();
+    		}
+    		// remove edges in opposite direction
+    		if(first.equals(v) == false) {
+    			iterator.remove();
+    		}
+    	}
+    	int count=0;
+    	for(E other : commonEdgeSet) {
+    		if(e.equals(other) == false) {
+    			edge_index.put(Context.<Graph<V,E>,E>getInstance(graph,other), count);
+    			count++;
+    		}
+    	}
+    	edge_index.put(Context.<Graph<V,E>,E>getInstance(graph,e), count);
+    	return count;
+     }
+    
+    protected int getIndex(Graph<V,E> graph, E e, V v) {
+    	Collection<E> commonEdgeSet = new HashSet<E>();
+    	for(E another : graph.getIncidentEdges(v)) {
+    		V u = graph.getOpposite(v, another);
+    		if(u.equals(v)) {
+    			commonEdgeSet.add(another);
+    		}
+    	}
+    	int count=0;
+    	for(E other : commonEdgeSet) {
+    		if(e.equals(other) == false) {
+    			edge_index.put(Context.<Graph<V,E>,E>getInstance(graph,other), count);
+    			count++;
+    		}
+    	}
+    	edge_index.put(Context.<Graph<V,E>,E>getInstance(graph,e), count);
+    	return count;
+    }
+
+    
+    /**
+     * Resets the indices for this edge and its parallel edges.
+     * Should be invoked when an edge parallel to <code>e</code>
+     * has been added or removed.
+     * @param graph the graph for which the indices are to be reset
+     * @param e the edge whose indices are to be reset
+     * 
+     */
+    public void reset(Graph<V,E> graph, E e) {
+    	Pair<V> endpoints = graph.getEndpoints(e);
+        getIndex(graph, e, endpoints.getFirst());
+        getIndex(graph, e, endpoints.getFirst(), endpoints.getSecond());
+    }
+    
+    /**
+     * Clears all edge indices for all edges in all graphs.
+     * Does not recalculate the indices.
+     */
+    public void reset()
+    {
+        edge_index.clear();
+    }
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/util/EdgeIndexFunction.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/EdgeIndexFunction.java
new file mode 100644
index 0000000..230ac7f
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/EdgeIndexFunction.java
@@ -0,0 +1,54 @@
+/*
+ * Created on Sep 24, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph.util;
+
+import edu.uci.ics.jung.graph.Graph;
+
+
+/**
+ * An interface for a service to access the index of a given edge (in a given graph)
+ * into the set formed by the given edge and all the other edges it is parallel to.
+ * 
+ * <p>Note that in current use, this index is assumed to be an integer value in
+ * the interval [0,n-1], where n-1 is the number of edges parallel to <code>e</code>.
+ * 
+ * @author Tom Nelson 
+ *
+ */
+public interface EdgeIndexFunction<V,E> {
+    
+    /**
+     * Returns <code>e</code>'s index in <code>graph</code>.
+     * The index of <code>e</code> is defined as its position in some 
+     * consistent ordering of <code>e</code> and all edges parallel to <code>e</code>.
+     * @param graph the graph with respect to which the index is calculated
+     * @param e the edge whose index is to be queried
+     * @return <code>e</code>'s index in <code>graph</code>
+     */
+    int getIndex(Graph<V, E> graph, E e);
+    
+    /**
+     * Resets the indices for <code>edge</code> and its parallel edges in <code>graph</code>.
+     * Should be invoked when an edge parallel to <code>edge</code>
+     * has been added or removed.
+     * 
+     * @param g the graph in which <code>edge</code>'s index is to be reset
+     * @param edge the edge whose index is to be reset
+     */
+    void reset(Graph<V,E> g, E edge);
+    
+    /**
+     * Clears all edge indices for all edges in all graphs.
+     * Does not recalculate the indices.
+     */
+    void reset();
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/util/EdgeType.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/EdgeType.java
new file mode 100644
index 0000000..160d20f
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/EdgeType.java
@@ -0,0 +1,22 @@
+/*
+ * Created on February 27, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.graph.util;
+
+/**
+ * Defines the possible edge types for graphs which assign types to edges.
+ */
+public enum EdgeType 
+{
+      DIRECTED,
+      UNDIRECTED
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/util/Graphs.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/Graphs.java
new file mode 100644
index 0000000..07f4904
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/Graphs.java
@@ -0,0 +1,989 @@
+package edu.uci.ics.jung.graph.util;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.Forest;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.Tree;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+/**
+ * Provides specialized implementations of <code>GraphDecorator</code>.  Currently these 
+ * wrapper types include "synchronized" and "unmodifiable".
+ *
+ * <p>The methods of this class may each throw a <code>NullPointerException</code>
+ * if the graphs or class objects provided to them are null.
+ *
+ * @author Tom Nelson
+ */
+
+public class Graphs {
+	
+	/**
+	 * Returns a synchronized graph backed by the passed argument graph.
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+	 * @param graph the graph for which a synchronized wrapper is to be created
+	 * @return a synchronized graph backed by the passed argument graph
+	 */
+	public static <V,E> Graph<V,E> synchronizedGraph(Graph<V,E> graph) {
+		return new SynchronizedGraph<V,E>(graph);
+	}
+	
+	/**
+	 * Returns a synchronized DirectedGraph backed by the passed DirectedGraph.
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @param graph the graph for which a synchronized wrapper is to be created
+	 * @return a synchronized DirectedGraph backed by the passed DirectedGraph
+	 */
+	public static <V,E> DirectedGraph<V,E> synchronizedDirectedGraph(DirectedGraph<V,E> graph) {
+		return new SynchronizedDirectedGraph<V,E>(graph);
+	}
+	
+	/**
+	 * Returns a synchronized UndirectedGraph backed by the passed UndirectedGraph.
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @param graph the graph for which a synchronized wrapper is to be created
+	 * @return a synchronized UndirectedGraph backed by the passed UndirectedGraph
+	 */
+	public static <V,E> UndirectedGraph<V,E> synchronizedUndirectedGraph(UndirectedGraph<V,E> graph) {
+		return new SynchronizedUndirectedGraph<V,E>(graph);
+	}
+	
+	/**
+	 * Returns a synchronized Forest backed by the passed Forest.
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @param forest the forest for which a synchronized wrapper is to be created
+	 * @return a synchronized Forest backed by the passed Forest
+	 */
+	public static <V,E> SynchronizedForest<V,E> synchronizedForest(Forest<V,E> forest) {
+		return new SynchronizedForest<V,E>(forest);
+	}
+	
+	/**
+	 * Returns a synchronized Tree backed by the passed Tree.
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @param tree the tree for which a synchronized wrapper is to be created
+	 * @return a synchronized Tree backed by the passed Tree
+	 */
+	public static <V,E> SynchronizedTree<V,E> synchronizedTree(Tree<V,E> tree) {
+		return new SynchronizedTree<V,E>(tree);
+	}
+	
+	/**
+	 * Returns an unmodifiable Graph backed by the passed Graph.
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @param graph the graph for which the unmodifiable wrapper is to be returned
+	 * @return an unmodifiable Graph backed by the passed Graph
+	 */
+	public static <V,E> Graph<V,E> unmodifiableGraph(Graph<V,E> graph) {
+		return new UnmodifiableGraph<V,E>(graph);
+	}
+	
+	/**
+	 * Returns an unmodifiable <code>DirectedGraph</code> backed by the passed graph.
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+	 * @param graph the graph for which the unmodifiable wrapper is to be returned
+	 * @return an unmodifiable <code>DirectedGraph</code> backed by the passed graph
+	 */
+	public static <V,E> DirectedGraph<V,E> unmodifiableDirectedGraph(DirectedGraph<V,E> graph) {
+		return new UnmodifiableDirectedGraph<V,E>(graph);
+	}
+	
+	/**
+	 * Returns an unmodifiable <code>UndirectedGraph</code> backed by the passed graph.
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+	 * @param graph the graph for which the unmodifiable wrapper is to be returned
+	 * @return an unmodifiable <code>UndirectedGraph</code> backed by the passed graph
+	 */
+	public static <V,E> UndirectedGraph<V,E> unmodifiableUndirectedGraph(UndirectedGraph<V,E> graph) {
+		return new UnmodifiableUndirectedGraph<V,E>(graph);
+	}
+	
+	/**
+     * Returns an unmodifiable <code>Tree</code> backed by the passed tree.
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @param tree the tree for which the unmodifiable wrapper is to be returned
+     * @return an unmodifiable <code>Tree</code> backed by the passed tree
+	 */
+	public static <V,E> UnmodifiableTree<V,E> unmodifiableTree(Tree<V,E> tree) {
+		return new UnmodifiableTree<V,E>(tree);
+	}
+	
+    /**
+     * Returns an unmodifiable <code>Forest</code> backed by the passed forest.
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @param forest the forest for which the unmodifiable wrapper is to be returned
+     * @return an unmodifiable <code>Forest</code> backed by the passed forest
+     */
+	public static <V,E> UnmodifiableForest<V,E> unmodifiableForest(Forest<V,E> forest) {
+		return new UnmodifiableForest<V,E>(forest);
+	}
+	
+	
+	@SuppressWarnings("serial")
+	static abstract class SynchronizedAbstractGraph<V,E> implements Graph<V,E>, Serializable {
+		protected Graph<V,E> delegate;
+
+		private SynchronizedAbstractGraph(Graph<V, E> delegate) {
+			if(delegate == null) {
+				throw new NullPointerException();
+			}
+			this.delegate = delegate;
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getDefaultEdgeType()
+		 */
+		public EdgeType getDefaultEdgeType()
+		{
+			return delegate.getDefaultEdgeType();
+		}
+		
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#addEdge(Object, Object, Object, EdgeType)
+		 */
+		public synchronized boolean addEdge(E e, V v1, V v2, EdgeType edgeType) {
+			return delegate.addEdge(e, v1, v2, edgeType);
+		}
+
+        /**
+         * @see edu.uci.ics.jung.graph.Hypergraph#addEdge(Object, Collection, EdgeType)
+         */
+		public synchronized boolean addEdge(E e, Collection<? extends V> 
+			vertices, EdgeType edgeType) 
+		{
+			return delegate.addEdge(e, vertices, edgeType);
+		}
+		
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#addEdge(Object, Object, Object)
+		 */
+		public synchronized boolean addEdge(E e, V v1, V v2) {
+			return delegate.addEdge(e, v1, v2);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#addVertex(java.lang.Object)
+		 */
+		public synchronized boolean addVertex(V vertex) {
+			return delegate.addVertex(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#isIncident(java.lang.Object, java.lang.Object)
+		 */
+		public synchronized boolean isIncident(V vertex, E edge) {
+			return delegate.isIncident(vertex, edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#isNeighbor(java.lang.Object, java.lang.Object)
+		 */
+		public synchronized boolean isNeighbor(V v1, V v2) {
+			return delegate.isNeighbor(v1, v2);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#degree(java.lang.Object)
+		 */
+		public synchronized int degree(V vertex) {
+			return delegate.degree(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#findEdge(java.lang.Object, java.lang.Object)
+		 */
+		public synchronized E findEdge(V v1, V v2) {
+			return delegate.findEdge(v1, v2);
+		}
+
+        /**
+         * @see edu.uci.ics.jung.graph.Hypergraph#findEdgeSet(java.lang.Object, java.lang.Object)
+         */
+        public synchronized Collection<E> findEdgeSet(V v1, V v2)
+        {
+            return delegate.findEdgeSet(v1, v2);
+        }
+        
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getEdges()
+		 */
+		public synchronized Collection<E> getEdges() {
+			return delegate.getEdges();
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getEdges(EdgeType)
+		 */
+		public synchronized Collection<E> getEdges(EdgeType edgeType) {
+			return delegate.getEdges(edgeType);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getEndpoints(java.lang.Object)
+		 */
+		public synchronized Pair<V> getEndpoints(E edge) {
+			return delegate.getEndpoints(edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentEdges(java.lang.Object)
+		 */
+		public synchronized Collection<E> getIncidentEdges(V vertex) {
+			return delegate.getIncidentEdges(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentVertices(java.lang.Object)
+		 */
+		public synchronized Collection<V> getIncidentVertices(E edge) {
+			return delegate.getIncidentVertices(edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getInEdges(java.lang.Object)
+		 */
+		public synchronized Collection<E> getInEdges(V vertex) {
+			return delegate.getInEdges(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getNeighbors(java.lang.Object)
+		 */
+		public synchronized Collection<V> getNeighbors(V vertex) {
+			return delegate.getNeighbors(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getOpposite(java.lang.Object, java.lang.Object)
+		 */
+		public synchronized V getOpposite(V vertex, E edge) {
+			return delegate.getOpposite(vertex, edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getOutEdges(java.lang.Object)
+		 */
+		public synchronized Collection<E> getOutEdges(V vertex) {
+			return delegate.getOutEdges(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getPredecessors(java.lang.Object)
+		 */
+		public synchronized Collection<V> getPredecessors(V vertex) {
+			return delegate.getPredecessors(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getSuccessors(java.lang.Object)
+		 */
+		public synchronized Collection<V> getSuccessors(V vertex) {
+			return delegate.getSuccessors(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getVertices()
+		 */
+		public synchronized Collection<V> getVertices() {
+			return delegate.getVertices();
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount()
+		 */
+		public synchronized int getEdgeCount() {
+			return delegate.getEdgeCount();
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount(EdgeType)
+		 */
+		public synchronized int getEdgeCount(EdgeType edge_type) 
+		{
+		    return delegate.getEdgeCount(edge_type);
+		}
+		
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getVertexCount()
+		 */
+		public synchronized int getVertexCount() {
+			return delegate.getVertexCount();
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#inDegree(java.lang.Object)
+		 */
+		public synchronized int inDegree(V vertex) {
+			return delegate.inDegree(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getEdgeType(java.lang.Object)
+		 */
+		public synchronized EdgeType getEdgeType(E edge) {
+			return delegate.getEdgeType(edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#isPredecessor(java.lang.Object, java.lang.Object)
+		 */
+		public synchronized boolean isPredecessor(V v1, V v2) {
+			return delegate.isPredecessor(v1, v2);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#isSuccessor(java.lang.Object, java.lang.Object)
+		 */
+		public synchronized boolean isSuccessor(V v1, V v2) {
+			return delegate.isSuccessor(v1, v2);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getNeighborCount(java.lang.Object)
+		 */
+		public synchronized int getNeighborCount(V vertex) {
+			return delegate.getNeighborCount(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getPredecessorCount(java.lang.Object)
+		 */
+		public synchronized int getPredecessorCount(V vertex) {
+			return delegate.getPredecessorCount(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getSuccessorCount(java.lang.Object)
+		 */
+		public synchronized int getSuccessorCount(V vertex) {
+			return delegate.getSuccessorCount(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#outDegree(java.lang.Object)
+		 */
+		public synchronized int outDegree(V vertex) {
+			return delegate.outDegree(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object)
+		 */
+		public synchronized boolean removeEdge(E edge) {
+			return delegate.removeEdge(edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object)
+		 */
+		public synchronized boolean removeVertex(V vertex) {
+			return delegate.removeVertex(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getDest(java.lang.Object)
+		 */
+		public synchronized V getDest(E directed_edge) {
+			return delegate.getDest(directed_edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getSource(java.lang.Object)
+		 */
+		public synchronized V getSource(E directed_edge) {
+			return delegate.getSource(directed_edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#isDest(java.lang.Object, java.lang.Object)
+		 */
+		public synchronized boolean isDest(V vertex, E edge) {
+			return delegate.isDest(vertex, edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#isSource(java.lang.Object, java.lang.Object)
+		 */
+		public synchronized boolean isSource(V vertex, E edge) {
+			return delegate.isSource(vertex, edge);
+		}
+		
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentCount(Object)
+		 */
+        public synchronized int getIncidentCount(E edge)
+        {
+            return delegate.getIncidentCount(edge);
+        }
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#addEdge(java.lang.Object, java.util.Collection)
+		 */
+		public synchronized boolean addEdge(E hyperedge, Collection<? extends V> vertices) {
+			return delegate.addEdge(hyperedge, vertices);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#containsEdge(java.lang.Object)
+		 */
+		public synchronized boolean containsEdge(E edge) {
+			return delegate.containsEdge(edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#containsVertex(java.lang.Object)
+		 */
+		public synchronized boolean containsVertex(V vertex) {
+			return delegate.containsVertex(vertex);
+		}
+        
+	}
+	
+	@SuppressWarnings("serial")
+    static class SynchronizedGraph<V,E> extends SynchronizedAbstractGraph<V,E> implements Serializable {
+		
+		private SynchronizedGraph(Graph<V,E> delegate) {
+			super(delegate);
+		}
+	}
+	
+	@SuppressWarnings("serial")
+    static class SynchronizedUndirectedGraph<V,E> extends SynchronizedAbstractGraph<V,E> 
+		implements UndirectedGraph<V,E>, Serializable {
+		private SynchronizedUndirectedGraph(UndirectedGraph<V,E> delegate) {
+			super(delegate);
+		}
+	}
+	
+	@SuppressWarnings("serial")
+    static class SynchronizedDirectedGraph<V,E> extends SynchronizedAbstractGraph<V,E> 
+		implements DirectedGraph<V,E>, Serializable {
+		
+		private SynchronizedDirectedGraph(DirectedGraph<V,E> delegate) {
+			super(delegate);
+		}
+
+		@Override
+	    public synchronized V getDest(E directed_edge) {
+			return ((DirectedGraph<V,E>)delegate).getDest(directed_edge);
+		}
+
+		@Override
+    	public synchronized V getSource(E directed_edge) {
+			return ((DirectedGraph<V,E>)delegate).getSource(directed_edge);
+		}
+
+		@Override
+	    public synchronized boolean isDest(V vertex, E edge) {
+			return ((DirectedGraph<V,E>)delegate).isDest(vertex, edge);
+		}
+
+		@Override
+    	public synchronized boolean isSource(V vertex, E edge) {
+			return ((DirectedGraph<V,E>)delegate).isSource(vertex, edge);
+		}
+	}
+	
+	@SuppressWarnings("serial")
+    static class SynchronizedTree<V,E> extends SynchronizedForest<V,E> implements Tree<V,E> {
+
+		/**
+		 * Creates a new instance based on the provided {@code delegate}.
+		 * @param delegate
+		 */
+		public SynchronizedTree(Tree<V, E> delegate) {
+			super(delegate);
+		}
+
+		public synchronized int getDepth(V vertex) {
+			return ((Tree<V,E>)delegate).getDepth(vertex);
+		}
+
+		public synchronized int getHeight() {
+			return ((Tree<V,E>)delegate).getHeight();
+		}
+
+		public synchronized V getRoot() {
+			return ((Tree<V,E>)delegate).getRoot();
+		}
+	}
+	
+	@SuppressWarnings("serial")
+    static class SynchronizedForest<V,E> extends SynchronizedDirectedGraph<V,E> implements Forest<V,E> {
+
+        /**
+         * Creates a new instance based on the provided {@code delegate}.
+         * @param delegate
+         */
+		public SynchronizedForest(Forest<V, E> delegate) {
+			super(delegate);
+		}
+
+		public synchronized Collection<Tree<V, E>> getTrees() {
+			return ((Forest<V,E>)delegate).getTrees();
+		}
+
+        public int getChildCount(V vertex)
+        {
+            return ((Forest<V,E>)delegate).getChildCount(vertex);
+        }
+
+        public Collection<E> getChildEdges(V vertex)
+        {
+            return ((Forest<V,E>)delegate).getChildEdges(vertex);
+        }
+
+        public Collection<V> getChildren(V vertex)
+        {
+            return ((Forest<V,E>)delegate).getChildren(vertex);
+        }
+
+        public V getParent(V vertex)
+        {
+            return ((Forest<V,E>)delegate).getParent(vertex);
+        }
+
+        public E getParentEdge(V vertex)
+        {
+            return ((Forest<V,E>)delegate).getParentEdge(vertex);
+        }
+	}
+	
+	@SuppressWarnings("serial")
+	static abstract class UnmodifiableAbstractGraph<V,E> implements Graph<V,E>, Serializable {
+		protected Graph<V,E> delegate;
+
+
+		private UnmodifiableAbstractGraph(Graph<V, E> delegate) {
+			if(delegate == null) {
+				throw new NullPointerException();
+			}
+			this.delegate = delegate;
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getDefaultEdgeType()
+		 */
+		public EdgeType getDefaultEdgeType()
+		{
+			return delegate.getDefaultEdgeType();
+		}
+		
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#addEdge(Object, Object, Object, EdgeType)
+		 */
+		public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) {
+			throw new UnsupportedOperationException();
+		}
+
+        /**
+         * @see edu.uci.ics.jung.graph.Graph#addEdge(Object, Collection, EdgeType)
+         */
+		public boolean addEdge(E e, Collection<? extends V> vertices, 
+			EdgeType edgeType) 
+		{
+			throw new UnsupportedOperationException();
+		}
+		
+        /**
+         * @see edu.uci.ics.jung.graph.Graph#addEdge(Object, Object, Object)
+         */
+		public boolean addEdge(E e, V v1, V v2) {
+			throw new UnsupportedOperationException();
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#addVertex(java.lang.Object)
+		 */
+		public boolean addVertex(V vertex) {
+			throw new UnsupportedOperationException();
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#isIncident(java.lang.Object, java.lang.Object)
+		 */
+		public boolean isIncident(V vertex, E edge) {
+			return delegate.isIncident(vertex, edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#isNeighbor(java.lang.Object, java.lang.Object)
+		 */
+		public boolean isNeighbor(V v1, V v2) {
+			return delegate.isNeighbor(v1, v2);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#degree(java.lang.Object)
+		 */
+		public int degree(V vertex) {
+			return delegate.degree(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#findEdge(java.lang.Object, java.lang.Object)
+		 */
+		public E findEdge(V v1, V v2) {
+			return delegate.findEdge(v1, v2);
+		}
+        
+        /**
+         * @see edu.uci.ics.jung.graph.Hypergraph#findEdgeSet(java.lang.Object, java.lang.Object)
+         */
+        public Collection<E> findEdgeSet(V v1, V v2)
+        {
+            return delegate.findEdgeSet(v1, v2);
+        }
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getEdges()
+		 */
+		public Collection<E> getEdges() {
+			return delegate.getEdges();
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount()
+		 */
+		public int getEdgeCount() {
+			return delegate.getEdgeCount();
+		}
+
+        /**
+         * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount(EdgeType)
+         */
+        public int getEdgeCount(EdgeType edge_type) 
+        {
+            return delegate.getEdgeCount(edge_type);
+        }
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getVertexCount()
+		 */
+		public int getVertexCount() {
+			return delegate.getVertexCount();
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getEdges(edu.uci.ics.jung.graph.util.EdgeType)
+		 */
+		public Collection<E> getEdges(EdgeType edgeType) {
+			return delegate.getEdges(edgeType);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getEndpoints(java.lang.Object)
+		 */
+		public Pair<V> getEndpoints(E edge) {
+			return delegate.getEndpoints(edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentEdges(java.lang.Object)
+		 */
+		public Collection<E> getIncidentEdges(V vertex) {
+			return delegate.getIncidentEdges(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentVertices(java.lang.Object)
+		 */
+		public Collection<V> getIncidentVertices(E edge) {
+			return delegate.getIncidentVertices(edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getInEdges(java.lang.Object)
+		 */
+		public Collection<E> getInEdges(V vertex) {
+			return delegate.getInEdges(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getNeighbors(java.lang.Object)
+		 */
+		public Collection<V> getNeighbors(V vertex) {
+			return delegate.getNeighbors(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getOpposite(java.lang.Object, java.lang.Object)
+		 */
+		public V getOpposite(V vertex, E edge) {
+			return delegate.getOpposite(vertex, edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getOutEdges(java.lang.Object)
+		 */
+		public Collection<E> getOutEdges(V vertex) {
+			return delegate.getOutEdges(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getPredecessors(java.lang.Object)
+		 */
+		public Collection<V> getPredecessors(V vertex) {
+			return delegate.getPredecessors(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getSuccessors(java.lang.Object)
+		 */
+		public Collection<V> getSuccessors(V vertex) {
+			return delegate.getSuccessors(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getVertices()
+		 */
+		public Collection<V> getVertices() {
+			return delegate.getVertices();
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#inDegree(java.lang.Object)
+		 */
+		public int inDegree(V vertex) {
+			return delegate.inDegree(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getEdgeType(java.lang.Object)
+		 */
+		public EdgeType getEdgeType(E edge) {
+			return delegate.getEdgeType(edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#isPredecessor(java.lang.Object, java.lang.Object)
+		 */
+		public boolean isPredecessor(V v1, V v2) {
+			return delegate.isPredecessor(v1, v2);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#isSuccessor(java.lang.Object, java.lang.Object)
+		 */
+		public boolean isSuccessor(V v1, V v2) {
+			return delegate.isSuccessor(v1, v2);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getNeighborCount(java.lang.Object)
+		 */
+		public int getNeighborCount(V vertex) {
+			return delegate.getNeighborCount(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getPredecessorCount(java.lang.Object)
+		 */
+		public int getPredecessorCount(V vertex) {
+			return delegate.getPredecessorCount(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getSuccessorCount(java.lang.Object)
+		 */
+		public int getSuccessorCount(V vertex) {
+			return delegate.getSuccessorCount(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#outDegree(java.lang.Object)
+		 */
+		public int outDegree(V vertex) {
+			return delegate.outDegree(vertex);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object)
+		 */
+		public boolean removeEdge(E edge) {
+			throw new UnsupportedOperationException();
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object)
+		 */
+		public boolean removeVertex(V vertex) {
+			throw new UnsupportedOperationException();
+		}
+		
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getDest(java.lang.Object)
+		 */
+		public V getDest(E directed_edge) {
+			return delegate.getDest(directed_edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#getSource(java.lang.Object)
+		 */
+		public V getSource(E directed_edge) {
+			return delegate.getSource(directed_edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#isDest(java.lang.Object, java.lang.Object)
+		 */
+		public boolean isDest(V vertex, E edge) {
+			return delegate.isDest(vertex, edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Graph#isSource(java.lang.Object, java.lang.Object)
+		 */
+		public boolean isSource(V vertex, E edge) {
+			return delegate.isSource(vertex, edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentCount(Object)
+		 */
+        public int getIncidentCount(E edge)
+        {
+            return delegate.getIncidentCount(edge);
+        }
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#addEdge(java.lang.Object, java.util.Collection)
+		 */
+		public boolean addEdge(E hyperedge, Collection<? extends V> vertices) {
+			return delegate.addEdge(hyperedge, vertices);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#containsEdge(java.lang.Object)
+		 */
+		public boolean containsEdge(E edge) {
+			return delegate.containsEdge(edge);
+		}
+
+		/**
+		 * @see edu.uci.ics.jung.graph.Hypergraph#containsVertex(java.lang.Object)
+		 */
+		public boolean containsVertex(V vertex) {
+			return delegate.containsVertex(vertex);
+		}
+	}
+	
+	@SuppressWarnings("serial")
+    static class UnmodifiableGraph<V,E> extends UnmodifiableAbstractGraph<V,E> implements Serializable {
+		private UnmodifiableGraph(Graph<V,E> delegate) {
+			super(delegate);
+		}
+	}
+	
+	@SuppressWarnings("serial")
+    static class UnmodifiableDirectedGraph<V,E> extends UnmodifiableAbstractGraph<V,E> 
+		implements DirectedGraph<V,E>, Serializable {
+		private UnmodifiableDirectedGraph(DirectedGraph<V,E> delegate) {
+			super(delegate);
+		}
+
+		@Override
+	    public V getDest(E directed_edge) {
+			return ((DirectedGraph<V,E>)delegate).getDest(directed_edge);
+		}
+
+		@Override
+    	public V getSource(E directed_edge) {
+			return ((DirectedGraph<V,E>)delegate).getSource(directed_edge);
+		}
+
+		@Override
+    	public boolean isDest(V vertex, E edge) {
+			return ((DirectedGraph<V,E>)delegate).isDest(vertex, edge);
+		}
+
+		@Override
+    	public boolean isSource(V vertex, E edge) {
+			return ((DirectedGraph<V,E>)delegate).isSource(vertex, edge);
+		}
+	}
+	
+	@SuppressWarnings("serial")
+    static class UnmodifiableUndirectedGraph<V,E> extends UnmodifiableAbstractGraph<V,E> 
+		implements UndirectedGraph<V,E>, Serializable {
+		private UnmodifiableUndirectedGraph(UndirectedGraph<V,E> delegate) {
+			super(delegate);
+		}
+	}
+	
+	@SuppressWarnings("serial")
+    static class UnmodifiableForest<V,E> extends UnmodifiableGraph<V,E>
+		implements Forest<V,E>, Serializable {
+		private UnmodifiableForest(Forest<V,E> delegate) {
+			super(delegate);
+		}
+
+		public Collection<Tree<V, E>> getTrees() {
+			return ((Forest<V,E>)delegate).getTrees();
+		}
+
+        public int getChildCount(V vertex)
+        {
+            return ((Forest<V,E>)delegate).getChildCount(vertex);
+        }
+
+        public Collection<E> getChildEdges(V vertex)
+        {
+            return ((Forest<V,E>)delegate).getChildEdges(vertex);
+        }
+
+        public Collection<V> getChildren(V vertex)
+        {
+            return ((Forest<V,E>)delegate).getChildren(vertex);
+        }
+
+        public V getParent(V vertex)
+        {
+            return ((Forest<V,E>)delegate).getParent(vertex);
+        }
+
+        public E getParentEdge(V vertex)
+        {
+            return ((Forest<V,E>)delegate).getParentEdge(vertex);
+        }
+	}
+	
+	@SuppressWarnings("serial")
+    static class UnmodifiableTree<V,E> extends UnmodifiableForest<V,E>
+	     implements Tree<V,E>, Serializable {
+		private UnmodifiableTree(Tree<V,E> delegate) {
+			super(delegate);
+		}
+
+		public int getDepth(V vertex) {
+			return ((Tree<V,E>)delegate).getDepth(vertex);
+		}
+
+		public int getHeight() {
+			return ((Tree<V,E>)delegate).getHeight();
+		}
+
+		public V getRoot() {
+			return ((Tree<V,E>)delegate).getRoot();
+		}
+
+		@Override
+    	public Collection<Tree<V, E>> getTrees() {
+			return ((Tree<V,E>)delegate).getTrees();
+		}
+	}
+
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/util/IncidentEdgeIndexFunction.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/IncidentEdgeIndexFunction.java
new file mode 100644
index 0000000..f462948
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/IncidentEdgeIndexFunction.java
@@ -0,0 +1,118 @@
+/*
+ * Created on Sep 24, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph.util;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * A class which creates and maintains indices for incident edges.
+ * 
+ * @author Tom Nelson
+ *
+ */
+public class IncidentEdgeIndexFunction<V,E> implements EdgeIndexFunction<V,E>
+{
+    protected Map<E, Integer> edge_index = new HashMap<E, Integer>();
+    
+    private IncidentEdgeIndexFunction() {
+    }
+    
+    /**
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @return an instance of this type.
+     */
+    public static <V,E> IncidentEdgeIndexFunction<V,E> getInstance() {
+        return new IncidentEdgeIndexFunction<V,E>();
+    }
+    
+    /**
+     * Returns the index for the specified edge.
+     * Calculates the indices for <code>e</code> and for all edges parallel
+     * to <code>e</code>.
+     */
+    public int getIndex(Graph<V, E> graph, E e)
+    {
+        Integer index = edge_index.get(e);
+        if(index == null) {
+        	Pair<V> endpoints = graph.getEndpoints(e);
+        	V u = endpoints.getFirst();
+        	V v = endpoints.getSecond();
+        	if(u.equals(v)) {
+        		index = getIndex(graph, e, v);
+        	} else {
+        		index = getIndex(graph, e, u, v);
+        	}
+        }
+        return index.intValue();
+    }
+
+    protected int getIndex(Graph<V,E> graph, E e, V u, V v) {
+    	Collection<E> commonEdgeSet = new HashSet<E>(graph.getIncidentEdges(u));
+    	int count=0;
+    	for(E other : commonEdgeSet) {
+    		if(e.equals(other) == false) {
+    			edge_index.put(other, count);
+    			count++;
+    		}
+    	}
+    	edge_index.put(e, count);
+    	return count;
+     }
+    
+    protected int getIndex(Graph<V,E> graph, E e, V v) {
+    	Collection<E> commonEdgeSet = new HashSet<E>();
+    	for(E another : graph.getIncidentEdges(v)) {
+    		V u = graph.getOpposite(v, another);
+    		if(u.equals(v)) {
+    			commonEdgeSet.add(another);
+    		}
+    	}
+    	int count=0;
+    	for(E other : commonEdgeSet) {
+    		if(e.equals(other) == false) {
+    			edge_index.put(other, count);
+    			count++;
+    		}
+    	}
+    	edge_index.put(e, count);
+    	return count;
+    }
+
+    
+    /**
+     * Resets the indices for this edge and its parallel edges.
+     * Should be invoked when an edge parallel to <code>e</code>
+     * has been added or removed.
+     * @param graph the graph whose indices are to be reset
+     * @param e the edge whose associated indices are to be reset
+     */
+    public void reset(Graph<V,E> graph, E e) {
+    	Pair<V> endpoints = graph.getEndpoints(e);
+        getIndex(graph, e, endpoints.getFirst());
+        getIndex(graph, e, endpoints.getFirst(), endpoints.getSecond());
+    }
+    
+    /**
+     * Clears all edge indices for all edges in all graphs.
+     * Does not recalculate the indices.
+     */
+    public void reset()
+    {
+        edge_index.clear();
+    }
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/util/Pair.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/Pair.java
new file mode 100644
index 0000000..f2d950d
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/Pair.java
@@ -0,0 +1,249 @@
+/*
+ * Created on Apr 2, 2006
+ *
+ * Copyright (c) 2006, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph.util;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Iterator;
+
+
+/**
+ * An implementation of <code>Collection</code> that stores exactly
+ * 2 non-null objects and is not mutable.  They respect <code>equals</code>
+ * and may be used as indices or map keys.<p>
+ * Note that they do not protect from malevolent behavior: if one or another
+ * object in the tuple is mutable, then it can be changed with the usual bad
+ * effects.
+ */
+ at SuppressWarnings("serial")
+public final class Pair<T> implements Collection<T>, Serializable
+{
+    private T first;
+    private T second;
+
+    /**
+     * Creates a <code>Pair</code> from the specified elements.
+     * @param value1 the first value in the new <code>Pair</code>
+     * @param value2 the second value in the new <code>Pair</code>
+     * @throws IllegalArgumentException if either argument is null
+     */
+    public Pair(T value1, T value2) 
+    {
+    	if(value1 == null || value2 == null) 
+    		throw new IllegalArgumentException("Pair cannot contain null values");
+        first = value1;
+        second = value2;
+    }
+    
+    /**
+     * Creates a Pair from the passed Collection.
+     * The size of the Collection must be 2.
+     * @param values the elements of the new <code>Pair</code>
+     * @throws IllegalArgumentException if the input collection is null,
+     * contains null values, or has != 2 elements.
+     */
+    public Pair(Collection<? extends T> values) 
+    {
+        if (values == null)
+            throw new IllegalArgumentException("Input collection cannot be null");
+    	if (values.size() == 2)
+        {
+            if(values.contains(null)) 
+                throw new IllegalArgumentException("Pair cannot contain null values");
+            Iterator<? extends T> iter = values.iterator();
+            first = iter.next();
+            second = iter.next();
+       }
+        else
+            throw new IllegalArgumentException("Pair may only be created from a Collection of exactly 2 elements");
+        
+    }
+    
+    /**
+     * Creates a <code>Pair</code> from the passed array.
+     * The size of the array must be 2.
+     * 
+     * @param values the values to be used to construct this Pair
+     * @throws IllegalArgumentException if the input array is null,
+     * contains null values, or has != 2 elements.
+     */
+    public Pair(T[] values)
+    {
+        if (values == null)
+            throw new IllegalArgumentException("Input array cannot be null");
+        if (values.length == 2)
+        {
+            if(values[0] == null || values[1] == null) 
+                throw new IllegalArgumentException("Pair cannot contain null values");
+            first = values[0];
+            second = values[1];
+        }
+        else
+            throw new IllegalArgumentException("Pair may only be created from an " +
+            		"array of 2 elements");
+    }
+
+    /**
+     * @return the first element.
+     */
+    public T getFirst() 
+    {
+        return first;
+    }
+    
+    /**
+     * @return the second element.
+     */
+    public T getSecond() 
+    {
+        return second;
+    }
+    
+    @Override
+    public boolean equals( Object o ) {
+        if (o == this)
+            return true;
+
+        if (o instanceof Pair) {
+            @SuppressWarnings("rawtypes")
+			Pair otherPair = (Pair) o;
+            Object otherFirst = otherPair.getFirst();
+            Object otherSecond = otherPair.getSecond();
+            return 
+            	(this.first  == otherFirst  || 
+            			(this.first != null  && this.first.equals(otherFirst)))   
+            			&&
+                (this.second == otherSecond || 
+                		(this.second != null && this.second.equals(otherSecond)));
+        } else {
+            return false;
+        }
+    }
+    
+    @Override
+    public int hashCode() 
+    {
+    	int hashCode = 1;
+	    hashCode = 31*hashCode + (first==null ? 0 : first.hashCode());
+	    hashCode = 31*hashCode + (second==null ? 0 : second.hashCode());
+    	return hashCode;
+    }
+    
+    @Override
+    public String toString()
+    {
+        return "<" + first.toString() + ", " + second.toString() + ">";
+    }
+
+    public boolean add(T o) {
+        throw new UnsupportedOperationException("Pairs cannot be mutated");
+    }
+
+    public boolean addAll(Collection<? extends T> c) {
+        throw new UnsupportedOperationException("Pairs cannot be mutated");
+    }
+
+    public void clear() {
+        throw new UnsupportedOperationException("Pairs cannot be mutated");
+    }
+
+    public boolean contains(Object o) {
+        return (first == o || first.equals(o) || second == o || second.equals(o));
+    }
+
+    public boolean containsAll(Collection<?> c) {
+        if (c.size() > 2)
+            return false;
+        Iterator<?> iter = c.iterator();
+        Object c_first = iter.next();
+        Object c_second = iter.next();
+        return this.contains(c_first) && this.contains(c_second);
+    }
+
+    public boolean isEmpty() {
+        return false;
+    }
+
+    public Iterator<T> iterator() {
+        return new PairIterator();
+    }
+
+    public boolean remove(Object o) {
+        throw new UnsupportedOperationException("Pairs cannot be mutated");
+    }
+
+    public boolean removeAll(Collection<?> c) {
+        throw new UnsupportedOperationException("Pairs cannot be mutated");
+    }
+
+    public boolean retainAll(Collection<?> c) {
+        throw new UnsupportedOperationException("Pairs cannot be mutated");
+    }
+
+    public int size() {
+        return 2;
+    }
+
+    public Object[] toArray() {
+        Object[] to_return = new Object[2];
+        to_return[0] = first;
+        to_return[1] = second;
+        return to_return;
+    }
+
+    @SuppressWarnings("unchecked")
+    public <S> S[] toArray(S[] a) {
+        S[] to_return = a;
+        Class<?> type = a.getClass().getComponentType();
+        if (a.length < 2)
+            to_return = (S[])java.lang.reflect.Array.newInstance(type, 2);
+        to_return[0] = (S)first;
+        to_return[1] = (S)second;
+        
+        if (to_return.length > 2)
+            to_return[2] = null;
+        return to_return;
+    }
+    
+    private class PairIterator implements Iterator<T>
+    {
+        int position;
+        
+        private PairIterator()
+        {
+            position = 0;
+        }
+
+        public boolean hasNext()
+        {
+            return position < 2;
+        }
+
+        public T next()
+        {
+            position++;
+            if (position == 1)
+                return first;
+            else if (position == 2)
+                return second;
+            else
+                return null;
+        }
+
+        public void remove()
+        {
+            throw new UnsupportedOperationException("Pairs cannot be mutated");
+        }
+    }
+}
+
+
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/util/TreeUtils.java b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/TreeUtils.java
new file mode 100644
index 0000000..4d76a4a
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/TreeUtils.java
@@ -0,0 +1,120 @@
+/*
+ * Created on Mar 3, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph.util;
+
+import edu.uci.ics.jung.graph.Forest;
+import edu.uci.ics.jung.graph.Tree;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Contains static methods for operating on instances of <code>Tree</code>.
+ */
+public class TreeUtils 
+{
+	/**
+	 * @param <V> the vertex type
+	 * @param <E> the edge type
+	 * @param forest the forest whose roots are to be returned
+	 * @return the roots of this forest.
+	 */
+	public static <V,E> List<V> getRoots(Forest<V,E> forest) 
+	{
+        List<V> roots = new ArrayList<V>();
+        for(Tree<V,E> tree : forest.getTrees()) {
+            roots.add(tree.getRoot());
+        }
+        return roots;
+	}
+    
+    /**
+     * Returns the subtree of <code>tree</code> which is rooted at <code>root</code> as a <code>Forest</code>.
+     * The tree returned is an independent entity, although it uses the same vertex and edge objects.
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @param forest the tree whose subtree is to be extracted
+     * @param root the root of the subtree to be extracted
+     * @return the subtree of <code>tree</code> which is rooted at <code>root</code>
+     * @throws InstantiationException if a new tree of the same type cannot be created
+     * @throws IllegalAccessException if a new tree of the same type cannot be created
+     */
+	@SuppressWarnings("unchecked")
+	public static <V,E> Tree<V,E> getSubTree(Forest<V,E> forest, V root) throws InstantiationException, IllegalAccessException
+	{
+	    if (!forest.containsVertex(root))
+	        throw new IllegalArgumentException("Specified tree does not contain the specified root as a vertex");
+		Forest<V,E> subforest = forest.getClass().newInstance();
+		subforest.addVertex(root);
+		growSubTree(forest, subforest, root);
+		
+		return subforest.getTrees().iterator().next();
+	}
+	
+	/**
+     * Populates <code>subtree</code> with the subtree of <code>tree</code> 
+     * which is rooted at <code>root</code>.
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @param tree the tree whose subtree is to be extracted
+     * @param subTree the tree instance which is to be populated with the subtree of <code>tree</code>
+     * @param root the root of the subtree to be extracted
+	 */
+	public static <V,E> void growSubTree(Forest<V,E> tree, Forest<V,E> subTree, V root) {
+		if(tree.getSuccessorCount(root) > 0) {
+			Collection<E> edges = tree.getOutEdges(root);
+			for(E e : edges) {
+				subTree.addEdge(e, tree.getEndpoints(e));
+			}
+			Collection<V> kids = tree.getSuccessors(root);
+			for(V kid : kids) {
+				growSubTree(tree, subTree, kid);
+			}
+		}
+	}
+	
+	/**
+	 * Connects <code>subTree</code> to <code>tree</code> by attaching it as a child 
+	 * of <code>node</code> with edge <code>connectingEdge</code>.
+     * @param <V> the vertex type
+     * @param <E> the edge type
+     * @param tree the tree to which <code>subTree</code> is to be added
+     * @param subTree the tree which is to be grafted on to <code>tree</code>
+     * @param node the parent of <code>subTree</code> in its new position in <code>tree</code>
+	 * @param connectingEdge the edge used to connect <code>subtree</code>'s root as a child of <code>node</code>
+	 */
+	public static <V,E> void addSubTree(Forest<V,E> tree, Forest<V,E> subTree, 
+			V node, E connectingEdge) {
+        if (node != null && !tree.containsVertex(node))
+            throw new IllegalArgumentException("Specified tree does not contain the specified node as a vertex");
+		V root = subTree.getTrees().iterator().next().getRoot();
+		addFromSubTree(tree, subTree, connectingEdge, node, root);
+	}
+	
+	public static <V,E> void addFromSubTree(Forest<V,E> tree, Forest<V,E> subTree, 
+			E edge, V parent, V root) {
+
+		// add edge connecting parent and root to tree
+		if(edge != null && parent != null) {
+			tree.addEdge(edge, parent, root);
+		} else {
+			tree.addVertex(root);
+		}
+		
+		Collection<E> outEdges = subTree.getOutEdges(root);
+		for(E e : outEdges) {
+			V opposite = subTree.getOpposite(root, e);
+			addFromSubTree(tree, subTree, e, root, opposite);
+		}
+	}
+}
diff --git a/jung-api/src/main/java/edu/uci/ics/jung/graph/util/package.html b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/package.html
new file mode 100644
index 0000000..e40d233
--- /dev/null
+++ b/jung-api/src/main/java/edu/uci/ics/jung/graph/util/package.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Utility interfaces and classes for the JUNG API.  These include:
+
+<ul>
+<li><code>Context</code>: a wrapper for an element in the context of a specific graph
+<li>classes for maintaining edge indices (primarily for rendering)
+<li><code>Pair<T></code>: an implementation of <code>Collection</code> designed for 
+two-element immutable collections
+<li><code>Graphs</code>: facilitates the creation of special delegate
+types such as synchronized and unmodifiable graphs
+<li><code>TreeUtils</code>: utilities for trees and forests (subtree extraction, grafting, merging, etc.)
+</ul> 
+</body>
+</html>
diff --git a/jung-api/src/site/site.xml b/jung-api/src/site/site.xml
new file mode 100644
index 0000000..4f25bdf
--- /dev/null
+++ b/jung-api/src/site/site.xml
@@ -0,0 +1,14 @@
+<project name="${project.name}">
+  <bannerLeft>
+    <name>${project.name}</name>
+  </bannerLeft>
+  <body>
+    <links>
+      <item name="${project.name}" href="${project.url}"/>
+    </links>
+    <menu ref="parent" />
+    <menu ref="modules" />
+    <menu ref="reports" />
+  </body>
+</project>
+
diff --git a/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractDirectedSparseMultigraphTest.java b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractDirectedSparseMultigraphTest.java
new file mode 100644
index 0000000..fad1e42
--- /dev/null
+++ b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractDirectedSparseMultigraphTest.java
@@ -0,0 +1,131 @@
+package edu.uci.ics.jung.graph;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import junit.framework.TestCase;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+public abstract class AbstractDirectedSparseMultigraphTest extends TestCase {
+
+	protected Integer v0 = new Integer(0);
+    protected Integer v1 = new Integer(1);
+    protected Integer v2 = new Integer(2);
+    
+    protected Float e01 = new Float(.1f);
+    protected Float e10 = new Float(.2f);
+    protected Float e12 = new Float(.3f);
+    protected Float e21 = new Float(.4f);
+    
+    protected Graph<Integer,Number> graph;
+
+    public void testGetEdges() {
+        assertEquals(graph.getEdgeCount(), 4);
+    }
+
+    public void testGetVertices() {
+        assertEquals(graph.getVertexCount(), 3);
+    }
+
+    public void testAddVertex() {
+        int count = graph.getVertexCount();
+        graph.addVertex(new Integer(3));
+        assertEquals(graph.getVertexCount(), count+1);
+    }
+
+    public void testRemoveEndVertex() {
+        int vertexCount = graph.getVertexCount();
+        graph.removeVertex(v0);
+        assertEquals(vertexCount-1, graph.getVertexCount());
+        assertEquals(2, graph.getEdgeCount());
+    }
+
+    public void testRemoveMiddleVertex() {
+        int vertexCount = graph.getVertexCount();
+        graph.removeVertex(v1);
+        assertEquals(vertexCount-1, graph.getVertexCount());
+        assertEquals(0, graph.getEdgeCount());
+    }
+
+    public void testAddEdge() {
+        int edgeCount = graph.getEdgeCount();
+        graph.addEdge(new Double(.5), v0, v1);
+        assertEquals(graph.getEdgeCount(), edgeCount+1);
+    }
+
+    public void testRemoveEdge() {
+        int edgeCount = graph.getEdgeCount();
+        graph.removeEdge(e12);
+        assertEquals(graph.getEdgeCount(), edgeCount-1);
+    }
+    
+    public void testNullEndpoint() {
+    	try {
+    		graph.addEdge(.99, new Pair<Integer>(1,null));
+    		fail("should not be able to add an edge with a null endpoint");
+    	} catch(IllegalArgumentException e) {
+    		// all is well
+    	}
+    }
+
+    public void testGetInEdges() {
+        assertEquals(graph.getInEdges(v1).size(), 2);
+    }
+
+    public void testGetOutEdges() {
+        assertEquals(graph.getOutEdges(v1).size(), 2);
+    }
+
+    public void testGetPredecessors() {
+        assertTrue(graph.getPredecessors(v0).containsAll(Collections.singleton(v1)));
+    }
+
+    public void testGetSuccessors() {
+        assertTrue(graph.getPredecessors(v1).contains(v0));
+        assertTrue(graph.getPredecessors(v1).contains(v2));
+    }
+
+    public void testGetNeighbors() {
+        Collection<Integer> neighbors = graph.getNeighbors(v1);
+        assertTrue(neighbors.contains(v0));
+        assertTrue(neighbors.contains(v2));
+    }
+
+    public void testGetIncidentEdges() {
+        assertEquals(graph.getIncidentEdges(v0).size(), 2);
+    }
+
+    public void testFindEdge() {
+        Number edge = graph.findEdge(v1, v2);
+        assertTrue(edge == e12 || edge == e21);
+    }
+
+    public void testGetEndpoints() {
+        Pair<Integer> endpoints = graph.getEndpoints(e01);
+        assertTrue((endpoints.getFirst() == v0 && endpoints.getSecond() == v1) ||
+                endpoints.getFirst() == v1 && endpoints.getSecond() == v0);
+    }
+
+    public void testIsDirected() {
+        for(Number edge : graph.getEdges()) {
+            assertEquals(graph.getEdgeType(edge), EdgeType.DIRECTED);
+        }
+    }
+
+    public void testAddDirectedEdge() {
+        Float edge = new Float(.9);
+        graph.addEdge(edge, v1, v2, EdgeType.DIRECTED);
+        assertEquals(graph.getEdgeType(edge), EdgeType.DIRECTED);
+    }
+    
+    public void testAddUndirectedEdge() {
+        try {
+            graph.addEdge(new Float(.9), v1, v2, EdgeType.UNDIRECTED);
+            fail("Cannot add an undirected edge to this graph");
+        } catch(IllegalArgumentException uoe) {
+            // all is well
+        }
+    }
+
+}
diff --git a/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractHypergraphTest.java b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractHypergraphTest.java
new file mode 100644
index 0000000..5828538
--- /dev/null
+++ b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractHypergraphTest.java
@@ -0,0 +1,172 @@
+/*
+ * Created on Apr 21, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import junit.framework.TestCase;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.Pair;
+
+
+public abstract class AbstractHypergraphTest extends TestCase
+{
+    protected Supplier<? extends Hypergraph<Integer,Character>> factory;
+    protected Hypergraph<Integer,Character> h;
+    
+    public AbstractHypergraphTest(Supplier<? extends Hypergraph<Integer,Character>> factory)
+    {
+        this.factory = factory;
+    }
+    
+    @Override
+    public void runTest() throws Exception {
+        setUp();
+        testAddVertex();
+        testAddEdge();
+        testEdgeEndpoints();
+        tearDown();
+    }
+
+    /**
+     * test for the following:
+     * <ul>
+     * <li>add successful iff arg is not present
+     * <li>count increases by 1 iff add is successful
+     * <li>null vertex argument actively rejected
+     * <li>vertex reported as present iff add is successful
+     * </ul>
+     */
+    public void testAddVertex()
+    {
+        int count = h.getVertexCount();
+        assertTrue(h.addVertex(new Integer(1)));
+        assertEquals(count+1, h.getVertexCount());
+        assertTrue(h.containsVertex(1));
+        boolean success = false;
+        try
+        {
+            success = h.addVertex(null);
+            fail("Implementation should disallow null vertices");
+        }
+        catch (IllegalArgumentException iae) {}
+        catch (NullPointerException npe)
+        {
+            fail("Implementation should actively prevent null vertices");
+        }
+        assertFalse(success);
+        assertFalse(h.addVertex(1));
+        assertEquals(count+1, h.getVertexCount());
+        assertFalse(h.containsVertex(2));
+    }
+    
+    /**
+     * test for the following:
+     * <ul>
+     * <li>add successful iff edge is not present 
+     * <li>edge count increases by 1 iff add successful
+     * <li>null edge arg actively rejected
+     * <li>edge reported as present iff add is successful
+     * <li>throw if edge is present with different endpoints
+     * </ul>
+     */
+    public void testAddEdge()
+    {
+        int edge_count = h.getEdgeCount();
+        int vertex_count = h.getVertexCount();
+        Pair<Integer> p = new Pair<Integer>(2, 3);
+        assertTrue(h.addEdge('a', p));
+        assertEquals(edge_count+1, h.getEdgeCount());
+        assertEquals(vertex_count+2, h.getVertexCount());
+        assertTrue(h.containsEdge('a'));
+        boolean success = false;
+        try
+        {
+            success = h.addEdge('b', null);
+            fail("Implementation should disallow null pairs/collections");
+            success = h.addEdge(null, p);
+            fail("Implementation should disallow null edges");
+        }
+        catch (IllegalArgumentException iae) {}
+        catch (NullPointerException npe)
+        {
+            fail("Implementation should actively prevent null edges, pairs, and collections");
+        }
+        assertFalse(success);
+        // adding the same edge with an equal Pair should return false
+        assertFalse(h.addEdge('a', new Pair<Integer>(2,3)));
+        // adding the same edge with the same Pair should return false
+        assertFalse(h.addEdge('a', p));
+        try
+        {
+            success = h.addEdge('a', new Pair<Integer>(3,4));
+            fail("Implementation should disallow existing edge objects from connecting new pairs/collections");
+        }
+        catch (IllegalArgumentException iae) {}
+        assertEquals(edge_count+1, h.getEdgeCount());
+        assertFalse(h.containsEdge('b'));
+    }
+    
+    /**
+     * test for the following:
+     * <ul>
+     * <li>if Graph, reject # of endpoints != 2
+     * <li>otherwise, accept any (non-negative) number of endpoints
+     * 
+     * </ul>
+     *
+     */
+    public void testEdgeEndpoints()
+    {
+        Collection<Integer> c = new ArrayList<Integer>();
+        for (int i = 0; i < 10; i++)
+        {
+            try
+            {
+                h.addEdge((char)i, c);
+                c.add(i);
+            }
+            catch (IllegalArgumentException iae)
+            {
+                if (h instanceof Graph)
+                {
+                    if (c.size() == 2)
+                        fail("improperly rejected incident vertex collection " + c);
+                }
+                else
+                    fail("hypergraph implementations should accept any positive number of incident vertices");
+            }
+        }
+    }
+    
+    /**
+     * should return null if any of the following is true
+     * <ul>
+     * <li>v1 is null 
+     * <li>v2 is null
+     * <li>there is no edge connecting v1 to v2 in this graph
+     * </ul>
+     * otherwise should return _an_ edge connecting v1 to v2.
+     * May be directed or undirected (depending on the graph);
+     * may be any of the edges in the graph that so connect v1 and v2.
+     * 
+     * Must _not_ return any directed edge for which v1 and v2 are distinct
+     * and v2 is the source.
+     */
+    public void testFindEdge()
+    {
+        
+    }
+}
diff --git a/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractOrderedSparseMultigraphTest.java b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractOrderedSparseMultigraphTest.java
new file mode 100644
index 0000000..54ef35b
--- /dev/null
+++ b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractOrderedSparseMultigraphTest.java
@@ -0,0 +1,162 @@
+package edu.uci.ics.jung.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+public abstract class AbstractOrderedSparseMultigraphTest extends TestCase {
+
+    protected Integer v0 = 0;
+    protected Integer v1 = 1;
+    protected Integer v2 = 2;
+    protected Number e01 = .1;
+    protected Number e10 = .2;
+    protected Number e12 = .3;
+    protected Number e21 = .4;
+
+    protected Supplier<Number> vertexFactory = new Supplier<Number>() {
+    	int v=0;
+		public Number get() {
+			return v++;
+		}
+    };
+    protected Supplier<Number> edgeFactory = new Supplier<Number>() {
+    	int e=0;
+		public Number get() {
+			return e++;
+		}
+    };
+    
+    protected Graph<Number,Number> graph;
+    protected int vertexCount = 50;
+    protected Graph<Integer,Number> smallGraph;
+
+    public void testGetEdges() {
+        assertEquals(smallGraph.getEdgeCount(), 4);
+//        System.err.println("getEdges()="+graph.getEdges());
+    }
+
+    public void testGetVertices() {
+        assertEquals(smallGraph.getVertexCount(), 3);
+//        System.err.println("getVertices()="+graph.getVertices());
+    }
+
+    public void testAddVertex() {
+        int count = graph.getVertexCount();
+        graph.addVertex(count);
+        assertEquals(graph.getVertexCount(), count+1);
+    }
+
+    public void testRemoveEndVertex() {
+        int vertexCount = graph.getVertexCount();
+        int edgeCount = graph.getEdgeCount();
+        Collection<Number> incident = graph.getIncidentEdges(vertexCount-1);
+        graph.removeVertex(vertexCount-1);
+        assertEquals(vertexCount-1, graph.getVertexCount());
+        assertEquals(edgeCount - incident.size(), graph.getEdgeCount());
+    }
+
+    public void testRemoveMiddleVertex() {
+        int vertexCount = graph.getVertexCount();
+        int edgeCount = graph.getEdgeCount();
+        Collection<Number> incident = graph.getIncidentEdges(vertexCount/2);
+        graph.removeVertex(vertexCount/2);
+        assertEquals(vertexCount-1, graph.getVertexCount());
+        assertEquals(edgeCount - incident.size(), graph.getEdgeCount());
+    }
+
+    public void testAddEdge() {
+        int edgeCount = graph.getEdgeCount();
+        graph.addEdge(edgeFactory.get(), 0, 1);
+        assertEquals(graph.getEdgeCount(), edgeCount+1);
+    }
+    
+    public void testNullEndpoint() {
+    	try {
+    		graph.addEdge(edgeFactory.get(), new Pair<Number>(1,null));
+    		fail("should not be able to add an edge with a null endpoint");
+    	} catch(IllegalArgumentException e) {
+    		// all is well
+    	}
+    }
+
+
+    public void testRemoveEdge() {
+    	List<Number> edgeList = new ArrayList<Number>(graph.getEdges());
+        int edgeCount = graph.getEdgeCount();
+        graph.removeEdge(edgeList.get(edgeList.size()/2));
+        assertEquals(graph.getEdgeCount(), edgeCount-1);
+    }
+
+    public void testGetInOutEdges() {
+    	for(Number v : graph.getVertices()) {
+    		Collection<Number> incident = graph.getIncidentEdges(v);
+    		Collection<Number> in = graph.getInEdges(v);
+    		Collection<Number> out = graph.getOutEdges(v);
+    		assertTrue(incident.containsAll(in));
+    		assertTrue(incident.containsAll(out));
+    		for(Number e : in) {
+    			if(out.contains(e)) {
+    				assertTrue(graph.getEdgeType(e) == EdgeType.UNDIRECTED);
+    			}
+    		}
+    		for(Number e : out) {
+    			if(in.contains(e)) {
+    				assertTrue(graph.getEdgeType(e) == EdgeType.UNDIRECTED);
+    			}
+    		}
+    	}
+    	
+        assertEquals(smallGraph.getInEdges(v1).size(), 4);
+        assertEquals(smallGraph.getOutEdges(v1).size(), 3);
+        assertEquals(smallGraph.getOutEdges(v0).size(), 2);
+    }
+
+    public void testGetPredecessors() {
+        assertTrue(smallGraph.getPredecessors(v0).containsAll(Collections.singleton(v1)));
+    }
+
+    public void testGetSuccessors() {
+        assertTrue(smallGraph.getPredecessors(v1).contains(v0));
+        assertTrue(smallGraph.getPredecessors(v1).contains(v2));
+    }
+
+    public void testGetNeighbors() {
+        Collection<Integer> neighbors = smallGraph.getNeighbors(v1);
+        assertTrue(neighbors.contains(v0));
+        assertTrue(neighbors.contains(v2));
+    }
+
+    public void testGetIncidentEdges() {
+        assertEquals(smallGraph.getIncidentEdges(v0).size(), 2);
+    }
+
+    public void testFindEdge() {
+        Number edge = smallGraph.findEdge(v1, v2);
+        assertTrue(edge == e12 || edge == e21);
+    }
+
+    public void testGetEndpoints() {
+        Pair<Integer> endpoints = smallGraph.getEndpoints(e01);
+        assertTrue((endpoints.getFirst() == v0 && endpoints.getSecond() == v1) ||
+                endpoints.getFirst() == v1 && endpoints.getSecond() == v0);
+    }
+
+    public void testIsDirected() {
+        for(Number edge : smallGraph.getEdges()) {
+        	if(edge == e21) {
+        		assertEquals(smallGraph.getEdgeType(edge), EdgeType.DIRECTED);
+        	} else {
+        		assertEquals(smallGraph.getEdgeType(edge), EdgeType.UNDIRECTED);
+        	}
+        }
+    }
+}
diff --git a/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractSortedSparseMultigraphTest.java b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractSortedSparseMultigraphTest.java
new file mode 100644
index 0000000..0ad59c0
--- /dev/null
+++ b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractSortedSparseMultigraphTest.java
@@ -0,0 +1,164 @@
+package edu.uci.ics.jung.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+public abstract class AbstractSortedSparseMultigraphTest extends TestCase {
+
+	public static class Foo {}
+	public static class Bar {}
+	protected Integer v0 = 0;
+	protected Integer v1 = 1;
+	protected Integer v2 = 2;
+	protected Double e01 = .1;
+	protected Double e10 = .2;
+	protected Double e12 = .3;
+	protected Double e21 = .4;
+
+	protected Supplier<Number> vertexFactory = new Supplier<Number>() {
+    	int v=0;
+		public Number get() {
+			return v++;
+		}
+    };
+    protected Supplier<Double> edgeFactory = new Supplier<Double>() {
+    	double e=0;
+		public Double get() {
+			return e++;
+		}
+    };
+    
+    protected Graph<Integer,Double> graph;
+    protected int vertexCount = 50;
+    protected Graph<Integer,Double> smallGraph;
+
+    public void testGetEdges() {
+        assertEquals(smallGraph.getEdgeCount(), 4);
+//        System.err.println("getEdges()="+graph.getEdges());
+    }
+
+    public void testGetVertices() {
+        assertEquals(smallGraph.getVertexCount(), 3);
+//        System.err.println("getVertices()="+graph.getVertices());
+    }
+
+    public void testAddVertex() {
+        int count = graph.getVertexCount();
+        graph.addVertex(count);
+        assertEquals(graph.getVertexCount(), count+1);
+    }
+
+    public void testRemoveEndVertex() {
+        int vertexCount = graph.getVertexCount();
+        int edgeCount = graph.getEdgeCount();
+        Collection<Double> incident = graph.getIncidentEdges(vertexCount-1);
+        graph.removeVertex(vertexCount-1);
+        assertEquals(vertexCount-1, graph.getVertexCount());
+        assertEquals(edgeCount - incident.size(), graph.getEdgeCount());
+    }
+
+    public void testRemoveMiddleVertex() {
+        int vertexCount = graph.getVertexCount();
+        int edgeCount = graph.getEdgeCount();
+        Collection<Double> incident = graph.getIncidentEdges(vertexCount/2);
+        graph.removeVertex(vertexCount/2);
+        assertEquals(vertexCount-1, graph.getVertexCount());
+        assertEquals(edgeCount - incident.size(), graph.getEdgeCount());
+    }
+
+    public void testAddEdge() {
+        int edgeCount = graph.getEdgeCount();
+        graph.addEdge(edgeFactory.get(), 0, 1);
+        assertEquals(graph.getEdgeCount(), edgeCount+1);
+    }
+    
+    public void testNullEndpoint() {
+    	try {
+    		graph.addEdge(edgeFactory.get(), new Pair<Integer>(1,null));
+    		fail("should not be able to add an edge with a null endpoint");
+    	} catch(IllegalArgumentException e) {
+    		// all is well
+    	}
+    }
+
+
+    public void testRemoveEdge() {
+    	List<Double> edgeList = new ArrayList<Double>(graph.getEdges());
+        int edgeCount = graph.getEdgeCount();
+        graph.removeEdge(edgeList.get(edgeList.size()/2));
+        assertEquals(graph.getEdgeCount(), edgeCount-1);
+    }
+
+    public void testGetInOutEdges() {
+    	for(Integer v : graph.getVertices()) {
+    		Collection<Double> incident = graph.getIncidentEdges(v);
+    		Collection<Double> in = graph.getInEdges(v);
+    		Collection<Double> out = graph.getOutEdges(v);
+    		assertTrue(incident.containsAll(in));
+    		assertTrue(incident.containsAll(out));
+    		for(Double e : in) {
+    			if(out.contains(e)) {
+    				assertTrue(graph.getEdgeType(e) == EdgeType.UNDIRECTED);
+    			}
+    		}
+    		for(Double e : out) {
+    			if(in.contains(e)) {
+    				assertTrue(graph.getEdgeType(e) == EdgeType.UNDIRECTED);
+    			}
+    		}
+    	}
+    	
+        assertEquals(smallGraph.getInEdges(v1).size(), 4);
+        assertEquals(smallGraph.getOutEdges(v1).size(), 3);
+        assertEquals(smallGraph.getOutEdges(v0).size(), 2);
+    }
+
+    public void testGetPredecessors() {
+        assertTrue(smallGraph.getPredecessors(v0).containsAll(Collections.singleton(v1)));
+    }
+
+    public void testGetSuccessors() {
+        assertTrue(smallGraph.getPredecessors(v1).contains(v0));
+        assertTrue(smallGraph.getPredecessors(v1).contains(v2));
+    }
+
+    public void testGetNeighbors() {
+        Collection<Integer> neighbors = smallGraph.getNeighbors(v1);
+        assertTrue(neighbors.contains(v0));
+        assertTrue(neighbors.contains(v2));
+    }
+
+    public void testGetIncidentEdges() {
+        assertEquals(smallGraph.getIncidentEdges(v0).size(), 2);
+    }
+
+    public void testFindEdge() {
+        Number edge = smallGraph.findEdge(v1, v2);
+        assertTrue(edge == e12 || edge == e21);
+    }
+
+    public void testGetEndpoints() {
+        Pair<Integer> endpoints = smallGraph.getEndpoints(e01);
+        assertTrue((endpoints.getFirst() == v0 && endpoints.getSecond() == v1) ||
+                endpoints.getFirst() == v1 && endpoints.getSecond() == v0);
+    }
+
+    public void testIsDirected() {
+        for(Double edge : smallGraph.getEdges()) {
+        	if(edge == e21) {
+        		assertEquals(smallGraph.getEdgeType(edge), EdgeType.DIRECTED);
+        	} else {
+        		assertEquals(smallGraph.getEdgeType(edge), EdgeType.UNDIRECTED);
+        	}
+        }
+    }
+}
diff --git a/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractSparseMultigraphTest.java b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractSparseMultigraphTest.java
new file mode 100644
index 0000000..a5809cb
--- /dev/null
+++ b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractSparseMultigraphTest.java
@@ -0,0 +1,160 @@
+package edu.uci.ics.jung.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+public abstract class AbstractSparseMultigraphTest extends TestCase {
+
+    protected Integer v0 = 0;
+    protected Integer v1 = 1;
+    protected Integer v2 = 2;
+    protected Number e01 = .1;
+    protected Number e10 = .2;
+    protected Number e12 = .3;
+    protected Number e21 = .4;
+
+    protected Supplier<Number> vertexFactory = new Supplier<Number>() {
+    	int v=0;
+		public Number get() {
+			return v++;
+		}
+    };
+    protected Supplier<Number> edgeFactory = new Supplier<Number>() {
+    	int e=0;
+		public Number get() {
+			return e++;
+		}
+    };
+    
+    protected Graph<Number,Number> graph;
+    protected int vertexCount = 50;
+    protected Graph<Integer,Number> smallGraph;
+
+    public void testGetEdges() {
+        assertEquals(smallGraph.getEdgeCount(), 4);
+    }
+
+    public void testGetVertices() {
+        assertEquals(smallGraph.getVertexCount(), 3);
+    }
+
+    public void testAddVertex() {
+        int count = graph.getVertexCount();
+        graph.addVertex(count);
+        assertEquals(graph.getVertexCount(), count+1);
+    }
+
+    public void testRemoveEndVertex() {
+        int vertexCount = graph.getVertexCount();
+        int edgeCount = graph.getEdgeCount();
+        Collection<Number> incident = graph.getIncidentEdges(vertexCount-1);
+        graph.removeVertex(vertexCount-1);
+        assertEquals(vertexCount-1, graph.getVertexCount());
+        assertEquals(edgeCount - incident.size(), graph.getEdgeCount());
+    }
+
+    public void testRemoveMiddleVertex() {
+        int vertexCount = graph.getVertexCount();
+        int edgeCount = graph.getEdgeCount();
+        Collection<Number> incident = graph.getIncidentEdges(vertexCount/2);
+        graph.removeVertex(vertexCount/2);
+        assertEquals(vertexCount-1, graph.getVertexCount());
+        assertEquals(edgeCount - incident.size(), graph.getEdgeCount());
+    }
+
+    public void testAddEdge() {
+        int edgeCount = graph.getEdgeCount();
+        graph.addEdge(edgeFactory.get(), 0, 1);
+        assertEquals(graph.getEdgeCount(), edgeCount+1);
+    }
+    
+    public void testNullEndpoint() {
+    	try {
+    		graph.addEdge(edgeFactory.get(), new Pair<Number>(1,null));
+    		fail("should not be able to add an edge with a null endpoint");
+    	} catch(IllegalArgumentException e) {
+    		// all is well
+    	}
+    }
+
+
+    public void testRemoveEdge() {
+    	List<Number> edgeList = new ArrayList<Number>(graph.getEdges());
+        int edgeCount = graph.getEdgeCount();
+        graph.removeEdge(edgeList.get(edgeList.size()/2));
+        assertEquals(graph.getEdgeCount(), edgeCount-1);
+    }
+
+    public void testGetInOutEdges() {
+    	for(Number v : graph.getVertices()) {
+    		Collection<Number> incident = graph.getIncidentEdges(v);
+    		Collection<Number> in = graph.getInEdges(v);
+    		Collection<Number> out = graph.getOutEdges(v);
+    		assertTrue(incident.containsAll(in));
+    		assertTrue(incident.containsAll(out));
+    		for(Number e : in) {
+    			if(out.contains(e)) {
+    				assertTrue(graph.getEdgeType(e) == EdgeType.UNDIRECTED);
+    			}
+    		}
+    		for(Number e : out) {
+    			if(in.contains(e)) {
+    				assertTrue(graph.getEdgeType(e) == EdgeType.UNDIRECTED);
+    			}
+    		}
+    	}
+    	
+        assertEquals(smallGraph.getInEdges(v1).size(), 4);
+        assertEquals(smallGraph.getOutEdges(v1).size(), 3);
+        assertEquals(smallGraph.getOutEdges(v0).size(), 2);
+    }
+
+    public void testGetPredecessors() {
+        assertTrue(smallGraph.getPredecessors(v0).containsAll(Collections.singleton(v1)));
+    }
+
+    public void testGetSuccessors() {
+        assertTrue(smallGraph.getPredecessors(v1).contains(v0));
+        assertTrue(smallGraph.getPredecessors(v1).contains(v2));
+    }
+
+    public void testGetNeighbors() {
+        Collection<Integer> neighbors = smallGraph.getNeighbors(v1);
+        assertTrue(neighbors.contains(v0));
+        assertTrue(neighbors.contains(v2));
+    }
+
+    public void testGetIncidentEdges() {
+        assertEquals(smallGraph.getIncidentEdges(v0).size(), 2);
+    }
+
+    public void testFindEdge() {
+        Number edge = smallGraph.findEdge(v1, v2);
+        assertTrue(edge == e12 || edge == e21);
+    }
+
+    public void testGetEndpoints() {
+        Pair<Integer> endpoints = smallGraph.getEndpoints(e01);
+        assertTrue((endpoints.getFirst() == v0 && endpoints.getSecond() == v1) ||
+                endpoints.getFirst() == v1 && endpoints.getSecond() == v0);
+    }
+
+    public void testIsDirected() {
+        for(Number edge : smallGraph.getEdges()) {
+        	if(edge == e21) {
+        		assertEquals(smallGraph.getEdgeType(edge), EdgeType.DIRECTED);
+        	} else {
+        		assertEquals(smallGraph.getEdgeType(edge), EdgeType.UNDIRECTED);
+        	}
+        }
+    }
+}
diff --git a/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractSparseTreeTest.java b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractSparseTreeTest.java
new file mode 100644
index 0000000..35d6bf4
--- /dev/null
+++ b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractSparseTreeTest.java
@@ -0,0 +1,104 @@
+package edu.uci.ics.jung.graph;
+
+import junit.framework.TestCase;
+
+import com.google.common.base.Supplier;
+
+
+public abstract class AbstractSparseTreeTest extends TestCase {
+
+	protected Tree<String,Integer> tree;
+	protected Supplier<DirectedGraph<String,Integer>> graphFactory;
+	protected Supplier<Integer> edgeFactory;
+
+	public void testRemoveVertex() {
+		tree.addVertex("A");
+		tree.addEdge(edgeFactory.get(), "A", "B");
+		tree.addEdge(edgeFactory.get(), "A", "C");
+		tree.addEdge(edgeFactory.get(), "B", "E");
+		tree.addEdge(edgeFactory.get(), "B", "F");
+//		System.err.println("tree is "+tree);
+		tree.removeVertex("B");
+//		System.err.println("tree now "+tree);
+	}
+	
+	public void testSimpleTree() {
+		tree.addVertex("A");
+		tree.addEdge(edgeFactory.get(), "A", "B");
+		tree.addEdge(edgeFactory.get(), "A", "C");
+	}
+	
+	public void testCreateLoop() {
+		try {
+			tree.addVertex("A");
+			tree.addEdge(edgeFactory.get(), "A", "A");
+			fail("should not be able to addChild(v,v)");
+		} catch(IllegalArgumentException e) {
+			// all is well
+		}
+		try {
+			tree.addEdge(edgeFactory.get(), "A", "B");
+			tree.addEdge(edgeFactory.get(), "B", "A");
+			fail("should not allow loop");
+		} catch(IllegalArgumentException e) {
+			// all is well
+		}
+	}
+	
+	public void testHeightAndDepth() {
+		tree.addVertex("V0");
+        assertEquals(tree.getHeight(), 0);
+        assertEquals(tree.getDepth("V0"), 0);
+    	tree.addEdge(edgeFactory.get(), "V0", "V1");
+        assertEquals(tree.getHeight(), 1);
+        assertEquals(tree.getDepth("V1"), 1);
+    	tree.addEdge(edgeFactory.get(), "V0", "V2");
+        assertEquals(tree.getHeight(), 1);
+        assertEquals(tree.getDepth("V2"), 1);
+    	tree.addEdge(edgeFactory.get(), "V1", "V4");
+        assertEquals(tree.getHeight(), 2);
+        assertEquals(tree.getDepth("V4"), 2);
+    	tree.addEdge(edgeFactory.get(), "V2", "V3");
+        assertEquals(tree.getHeight(), 2);
+        assertEquals(tree.getDepth("V3"), 2);
+    	tree.addEdge(edgeFactory.get(), "V2", "V5");
+        assertEquals(tree.getHeight(), 2);
+        assertEquals(tree.getDepth("V5"), 2);
+    	tree.addEdge(edgeFactory.get(), "V4", "V6");
+        assertEquals(tree.getHeight(), 3);
+        assertEquals(tree.getDepth("V6"), 3);
+    	tree.addEdge(edgeFactory.get(), "V4", "V7");
+        assertEquals(tree.getHeight(), 3);
+        assertEquals(tree.getDepth("V7"), 3);
+    	tree.addEdge(edgeFactory.get(), "V3", "V8");
+        assertEquals(tree.getHeight(), 3);
+        assertEquals(tree.getDepth("V8"), 3);
+    	tree.addEdge(edgeFactory.get(), "V6", "V9");
+        assertEquals(tree.getHeight(), 4);
+        assertEquals(tree.getDepth("V9"), 4);
+    	tree.addEdge(edgeFactory.get(), "V4", "V10");
+        assertEquals(tree.getHeight(), 4);
+        assertEquals(tree.getDepth("V10"), 3);
+       	tree.addEdge(edgeFactory.get(), "V4", "V11");
+        assertEquals(tree.getHeight(), 4);
+        assertEquals(tree.getDepth("V11"), 3);
+       	tree.addEdge(edgeFactory.get(), "V4", "V12");
+        assertEquals(tree.getHeight(), 4);
+        assertEquals(tree.getDepth("V12"), 3);
+       	tree.addEdge(edgeFactory.get(), "V6", "V13");
+        assertEquals(tree.getHeight(), 4);
+        assertEquals(tree.getDepth("V13"), 4);
+       	tree.addEdge(edgeFactory.get(), "V10", "V14");
+        assertEquals(tree.getHeight(), 4);
+        assertEquals(tree.getDepth("V14"), 4);
+       	tree.addEdge(edgeFactory.get(), "V13", "V15");
+        assertEquals(tree.getHeight(), 5);
+        assertEquals(tree.getDepth("V15"), 5);
+       	tree.addEdge(edgeFactory.get(), "V13", "V16");
+       	assertEquals(tree.getHeight(), 5);
+        assertEquals(tree.getDepth("V16"), 5);
+
+	}
+	
+	
+}
diff --git a/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractTreeUtilsTest.java b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractTreeUtilsTest.java
new file mode 100644
index 0000000..64880bf
--- /dev/null
+++ b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractTreeUtilsTest.java
@@ -0,0 +1,43 @@
+package edu.uci.ics.jung.graph;
+
+import junit.framework.TestCase;
+import edu.uci.ics.jung.graph.util.TreeUtils;
+
+public abstract class AbstractTreeUtilsTest extends TestCase {
+	
+	protected Tree<String,Integer> tree;
+
+	public void testRemove() {
+		try {
+			TreeUtils.getSubTree(tree, "C0");
+			tree.removeVertex("C0");
+		} catch (InstantiationException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (IllegalAccessException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+	
+	public void testAdd() {
+		try {
+			Forest<String,Integer> subTree = TreeUtils.getSubTree(tree, "C0");
+			Integer edge = tree.getInEdges("C0").iterator().next();
+			String parent = tree.getPredecessors("C0").iterator().next();
+			tree.removeVertex("C0");
+			
+			TreeUtils.addSubTree(tree, subTree, parent, edge);
+		} catch (InstantiationException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		} catch (IllegalAccessException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		
+	}
+	
+	
+
+}
diff --git a/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractUndirectedSparseMultigraphTest.java b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractUndirectedSparseMultigraphTest.java
new file mode 100644
index 0000000..8264547
--- /dev/null
+++ b/jung-api/src/test/java/edu/uci/ics/jung/graph/AbstractUndirectedSparseMultigraphTest.java
@@ -0,0 +1,124 @@
+package edu.uci.ics.jung.graph;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import junit.framework.TestCase;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+public abstract class AbstractUndirectedSparseMultigraphTest extends TestCase {
+
+    protected Integer v0 = new Integer(0);
+    protected Integer v1 = new Integer(1);
+    protected Integer v2 = new Integer(2);
+    
+    protected Float e01 = new Float(.1f);
+    protected Float e10 = new Float(.2f);
+    protected Float e12 = new Float(.3f);
+    protected Float e21 = new Float(.4f);
+    
+    protected Graph<Integer,Number> graph;
+
+    public void testGetEdges() {
+        assertEquals(graph.getEdgeCount(), 4);
+    }
+
+    public void testGetVertices() {
+        assertEquals(graph.getVertexCount(), 3);
+    }
+
+    public void testAddVertex() {
+        int count = graph.getVertexCount();
+        graph.addVertex(new Integer(3));
+        assertEquals(graph.getVertexCount(), count+1);
+    }
+
+    public void testRemoveEndVertex() {
+        int vertexCount = graph.getVertexCount();
+        graph.removeVertex(v0);
+        assertEquals(vertexCount-1, graph.getVertexCount());
+        assertEquals(2, graph.getEdgeCount());
+    }
+
+    public void testRemoveMiddleVertex() {
+        int vertexCount = graph.getVertexCount();
+        graph.removeVertex(v1);
+        assertEquals(vertexCount-1, graph.getVertexCount());
+        assertEquals(0, graph.getEdgeCount());
+    }
+
+    public void testAddEdge() {
+        int edgeCount = graph.getEdgeCount();
+        graph.addEdge(new Double(.5), v0, v1);
+        assertEquals(graph.getEdgeCount(), edgeCount+1);
+    }
+
+    public void testRemoveEdge() {
+        int edgeCount = graph.getEdgeCount();
+        graph.removeEdge(e12);
+        assertEquals(graph.getEdgeCount(), edgeCount-1);
+    }
+
+    public void testGetInEdges() {
+        assertEquals(graph.getInEdges(v1).size(), 4);
+    }
+
+    public void testGetOutEdges() {
+        assertEquals(graph.getOutEdges(v1).size(), 4);
+    }
+
+    public void testGetPredecessors() {
+        assertTrue(graph.getPredecessors(v0).containsAll(Collections.singleton(v1)));
+    }
+
+    public void testGetSuccessors() {
+        assertTrue(graph.getPredecessors(v1).contains(v0));
+        assertTrue(graph.getPredecessors(v1).contains(v2));
+    }
+
+    public void testGetNeighbors() {
+        Collection<Integer> neighbors = graph.getNeighbors(v1);
+        assertTrue(neighbors.contains(v0));
+        assertTrue(neighbors.contains(v2));
+    }
+
+    public void testGetIncidentEdges() {
+        assertEquals(graph.getIncidentEdges(v0).size(), 2);
+    }
+
+    public void testFindEdge() {
+        Number edge = graph.findEdge(v1, v2);
+        assertTrue(edge == e12 || edge == e21);
+    }
+
+    public void testNullEndpoint() {
+    	try {
+    		graph.addEdge(.99, new Pair<Integer>(1,null));
+    		fail("should not be able to add an edge with a null endpoint");
+    	} catch(IllegalArgumentException e) {
+    		// all is well
+    	}
+    }
+
+    public void testGetEndpoints() {
+        Pair<Integer> endpoints = graph.getEndpoints(e01);
+        assertTrue((endpoints.getFirst() == v0 && endpoints.getSecond() == v1) ||
+                endpoints.getFirst() == v1 && endpoints.getSecond() == v0);
+    }
+
+    public void testIsDirected() {
+        for(Number edge : graph.getEdges()) {
+            assertEquals(graph.getEdgeType(edge), EdgeType.UNDIRECTED);
+        }
+    }
+
+    public void testAddDirectedEdge() {
+        try {
+            graph.addEdge(new Float(.9), v1, v2, EdgeType.DIRECTED);
+            fail("Cannot add a directed edge to this graph");
+        } catch(IllegalArgumentException uoe) {
+            // all is well
+        }
+    }
+}
diff --git a/jung-api/src/test/java/edu/uci/ics/jung/graph/util/PairTest.java b/jung-api/src/test/java/edu/uci/ics/jung/graph/util/PairTest.java
new file mode 100644
index 0000000..a5d7574
--- /dev/null
+++ b/jung-api/src/test/java/edu/uci/ics/jung/graph/util/PairTest.java
@@ -0,0 +1,118 @@
+package edu.uci.ics.jung.graph.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import edu.uci.ics.jung.graph.util.Pair;
+
+import junit.framework.TestCase;
+
+public class PairTest extends TestCase {
+    
+    Pair<Number> pair;
+    
+
+    @Override
+    protected void setUp() throws Exception {
+        pair = new Pair<Number>(1,2);
+        super.setUp();
+    }
+
+    public void testGetFirst() {
+        assertEquals(pair.getFirst(), 1);
+    }
+
+    public void testGetSecond() {
+        assertEquals(pair.getSecond(), 2);
+    }
+
+    public void testEqualsObject() {
+        Pair<Number> ipair = new Pair<Number>(1,2);
+        assertTrue(pair.equals(ipair));
+    }
+
+    public void testAdd() {
+        try {
+            pair.add(3);
+            fail("should not be able to add to Pair");
+        } catch(Exception e) { 
+            // all is well
+        }
+    }
+
+    public void testAddAll() {
+        try {
+            List<Number> list = new ArrayList<Number>(pair);
+            pair.addAll(list);
+            fail("should not be able to addAll to Pair");
+        } catch(Exception e) { 
+            // all is well
+        }
+    }
+
+    public void testClear() {
+        try {
+            pair.clear();
+            fail("should not be able to clear a Pair");
+        } catch(Exception e) { 
+            // all is well
+        }
+    }
+
+    public void testContains() {
+        assertTrue(pair.contains(1));
+    }
+
+    public void testContainsAll() {
+        List<Number> list = new ArrayList<Number>(pair);
+        assertTrue(pair.containsAll(list));
+    }
+
+    public void testIsEmpty() {
+        assertFalse(pair.isEmpty());
+    }
+
+    public void testRemove() {
+        try {
+            pair.remove(1);
+            fail("should not be able to remove from a Pair");
+        } catch(Exception e) { 
+            // all is well
+        }
+    }
+
+    public void testRemoveAll() {
+        try {
+            List<Number> list = new ArrayList<Number>(pair);
+            pair.removeAll(list);
+            fail("should not be able to removeAll from Pair");
+        } catch(Exception e) { 
+            // all is well
+        }
+    }
+
+    public void testRetainAll() {
+        try {
+            List<Number> list = new ArrayList<Number>(pair);
+            pair.retainAll(list);
+            fail("should not be able to retainAll from Pair");
+        } catch(Exception e) { 
+            // all is well
+        }
+    }
+
+    public void testSize() {
+        assertEquals(pair.size(), 2);
+    }
+
+    public void testToArray() {
+        @SuppressWarnings("unused")
+        Object[] arr = pair.toArray();
+    }
+
+    public void testToArraySArray() {
+        @SuppressWarnings("unused")
+        Integer[] arr = pair.<Integer>toArray(new Integer[2]);
+    }
+
+}
diff --git a/jung-graph-impl-2.0.1-sources.jar b/jung-graph-impl-2.0.1-sources.jar
deleted file mode 100644
index 2c1661a..0000000
Binary files a/jung-graph-impl-2.0.1-sources.jar and /dev/null differ
diff --git a/jung-graph-impl/assembly.xml b/jung-graph-impl/assembly.xml
new file mode 100644
index 0000000..81fb092
--- /dev/null
+++ b/jung-graph-impl/assembly.xml
@@ -0,0 +1,19 @@
+<assembly>
+  <id>dependencies</id>
+  <formats>
+    <format>tar.gz</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <fileSets>
+    <fileSet>
+      <outputDirectory>/</outputDirectory>
+    </fileSet>
+  </fileSets>
+  <dependencySets>
+    <dependencySet>
+      <outputDirectory>/</outputDirectory>
+      <unpack>false</unpack>
+      <scope>runtime</scope>
+    </dependencySet>
+  </dependencySets>
+</assembly>
diff --git a/jung-graph-impl/pom.xml b/jung-graph-impl/pom.xml
new file mode 100644
index 0000000..1bef394
--- /dev/null
+++ b/jung-graph-impl/pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>net.sf.jung</groupId>
+    <artifactId>jung-parent</artifactId>
+    <version>2.1.1</version>
+  </parent>
+  <artifactId>jung-graph-impl</artifactId>
+  <name>JUNG - Graph Implementations</name>
+  <description>Graph implementations for the JUNG project</description>
+  
+  <dependencies>
+	<dependency>
+		<groupId>net.sf.jung</groupId>
+		<artifactId>jung-api</artifactId>
+		<version>${project.version}</version>
+	</dependency>
+	<dependency>
+		<groupId>net.sf.jung</groupId>
+		<artifactId>jung-api</artifactId>
+		<version>${project.version}</version>
+		<type>test-jar</type>
+		<scope>test</scope>
+	</dependency>
+    <dependency>
+		<groupId>junit</groupId>
+		<artifactId>junit</artifactId>
+		<scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/AbstractGraph.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/AbstractGraph.java
new file mode 100644
index 0000000..ec2d38f
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/AbstractGraph.java
@@ -0,0 +1,244 @@
+/*
+ * Created on Apr 2, 2006
+ *
+ * Copyright (c) 2006, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * Abstract implementation of the <code>Graph</code> interface.  
+ * Designed to simplify implementation of new graph classes.
+ * 
+ * @author Joshua O'Madadhain
+ */
+ at SuppressWarnings("serial")
+public abstract class AbstractGraph<V, E> implements Graph<V,E>, Serializable 
+{
+	public boolean addEdge(E edge, Collection<? extends V> vertices) 
+	{
+		return addEdge(edge, vertices, this.getDefaultEdgeType());
+	}
+
+	@SuppressWarnings("unchecked")
+	public boolean addEdge(E edge, Collection<? extends V> vertices, EdgeType edgeType) {
+	    if (vertices == null)
+	        throw new IllegalArgumentException("'vertices' parameter must not be null");
+	    if (vertices.size() == 2)
+	        return addEdge(edge, 
+	        			   vertices instanceof Pair ? (Pair<V>)vertices : new Pair<V>(vertices), 
+	        			   edgeType);
+        else if (vertices.size() == 1)
+        {
+            V vertex = vertices.iterator().next();
+            return addEdge(edge, new Pair<V>(vertex, vertex), edgeType);
+        }
+        else
+            throw new IllegalArgumentException("Graph objects connect 1 or 2 vertices; vertices arg has " + vertices.size());
+	}
+	
+	public boolean addEdge(E e, V v1, V v2)
+	{
+		return addEdge(e, v1, v2, this.getDefaultEdgeType());
+	}
+
+	public boolean addEdge(E e, V v1, V v2, EdgeType edge_type)
+	{
+		return addEdge(e, new Pair<V>(v1, v2), edge_type);
+	}
+	
+	/**
+	 * Adds {@code edge} to this graph with the specified {@code endpoints},
+	 * with the default edge type.
+	 * 
+	 * @param edge the edge to be added
+	 * @param endpoints the endpoints to be connected to this edge
+	 * @return {@code true} iff the graph was modified as a result of this call
+	 */
+	public boolean addEdge(E edge, Pair<? extends V> endpoints)
+	{
+		return addEdge(edge, endpoints, this.getDefaultEdgeType());
+	}
+	
+    /**
+     * Adds {@code edge} to this graph with the specified {@code endpoints}
+     * and {@code EdgeType}.
+     * 
+	 * @param edge the edge to be added
+	 * @param endpoints the endpoints to be connected to this edge
+	 * @param edgeType the type of edge to add
+     * @return {@code} true iff the graph was modified as a result of this call
+     */
+	public abstract boolean addEdge(E edge, Pair<? extends V> endpoints, EdgeType edgeType);
+
+    protected Pair<V> getValidatedEndpoints(E edge, Pair<? extends V> endpoints)
+    {
+        if (edge == null)
+            throw new IllegalArgumentException("input edge may not be null");
+        
+        if (endpoints == null)
+            throw new IllegalArgumentException("endpoints may not be null");
+        
+        Pair<V> new_endpoints = new Pair<V>(endpoints.getFirst(), endpoints.getSecond());
+        if (containsEdge(edge))
+        {
+            Pair<V> existing_endpoints = getEndpoints(edge);
+            if (!existing_endpoints.equals(new_endpoints)) {
+                throw new IllegalArgumentException("edge " + edge + 
+                        " already exists in this graph with endpoints " + existing_endpoints + 
+                        " and cannot be added with endpoints " + endpoints);
+            } else {
+                return null;
+            }
+        }
+        return new_endpoints;
+    }
+    
+    public int inDegree(V vertex)
+    {
+        return this.getInEdges(vertex).size();
+    }
+
+    public int outDegree(V vertex)
+    {
+        return this.getOutEdges(vertex).size();
+    }
+
+    public boolean isPredecessor(V v1, V v2)
+    {
+        return this.getPredecessors(v1).contains(v2);
+    }
+
+    public boolean isSuccessor(V v1, V v2)
+    {
+        return this.getSuccessors(v1).contains(v2);
+    }
+
+    public int getPredecessorCount(V vertex)
+    {
+        return this.getPredecessors(vertex).size();
+    }
+
+    public int getSuccessorCount(V vertex)
+    {
+        return this.getSuccessors(vertex).size();
+    }
+
+    public boolean isNeighbor(V v1, V v2)
+    {
+        if (!containsVertex(v1) || !containsVertex(v2))
+            throw new IllegalArgumentException("At least one of these not in this graph: " + v1 + ", " + v2);
+        return this.getNeighbors(v1).contains(v2);
+    }
+
+    public boolean isIncident(V vertex, E edge)
+    {
+        if (!containsVertex(vertex) || !containsEdge(edge))
+            throw new IllegalArgumentException("At least one of these not in this graph: " + vertex + ", " + edge);
+        return this.getIncidentEdges(vertex).contains(edge);
+    }
+
+    public int getNeighborCount(V vertex)
+    {
+        if (!containsVertex(vertex))
+            throw new IllegalArgumentException(vertex + " is not a vertex in this graph");
+        return this.getNeighbors(vertex).size();
+    }
+
+    public int degree(V vertex)
+    {
+        if (!containsVertex(vertex))
+            throw new IllegalArgumentException(vertex + " is not a vertex in this graph");
+        return this.getIncidentEdges(vertex).size();
+    }
+
+    public int getIncidentCount(E edge)
+    {
+        Pair<V> incident = this.getEndpoints(edge);
+        if (incident == null)
+            return 0;
+        if (incident.getFirst() == incident.getSecond())
+            return 1;
+        else
+            return 2;
+    }
+    
+    public V getOpposite(V vertex, E edge)
+    {
+        Pair<V> incident = this.getEndpoints(edge); 
+        V first = incident.getFirst();
+        V second = incident.getSecond();
+        if (vertex.equals(first))
+            return second;
+        else if (vertex.equals(second))
+            return first;
+        else 
+            throw new IllegalArgumentException(vertex + " is not incident to " + edge + " in this graph");
+    }
+
+    public E findEdge(V v1, V v2)
+    {
+        for (E e : getOutEdges(v1))
+        {
+            if (getOpposite(v1, e).equals(v2))
+                return e;
+        }
+        return null;
+    }
+    
+    public Collection<E> findEdgeSet(V v1, V v2)
+    {
+        if (!getVertices().contains(v1))
+            throw new IllegalArgumentException(v1 + " is not an element of this graph");
+        
+        if (!getVertices().contains(v2))
+            throw new IllegalArgumentException(v2 + " is not an element of this graph");
+        
+        Collection<E> edges = new ArrayList<E>();
+        for (E e : getOutEdges(v1))
+        {
+            if (getOpposite(v1, e).equals(v2))
+                edges.add(e);
+        }
+        return Collections.unmodifiableCollection(edges);
+    }
+    
+    public Collection<V> getIncidentVertices(E edge)
+    {
+        Pair<V> endpoints = this.getEndpoints(edge);
+        Collection<V> incident = new ArrayList<V>();
+        incident.add(endpoints.getFirst());
+        incident.add(endpoints.getSecond());
+        
+        return Collections.unmodifiableCollection(incident);
+    }
+    
+    @Override
+    public String toString() {
+    	StringBuffer sb = new StringBuffer("Vertices:");
+    	for(V v : getVertices()) {
+    		sb.append(v+",");
+    	}
+    	sb.setLength(sb.length()-1);
+    	sb.append("\nEdges:");
+    	for(E e : getEdges()) {
+    		Pair<V> ep = getEndpoints(e);
+    		sb.append(e+"["+ep.getFirst()+","+ep.getSecond()+"] ");
+    	}
+        return sb.toString();
+    }
+
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/AbstractTypedGraph.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/AbstractTypedGraph.java
new file mode 100644
index 0000000..620d1c8
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/AbstractTypedGraph.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Sep 1, 2008
+ * 
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+/**
+ * An abstract class for graphs whose edges all have the same {@code EdgeType}.
+ * Intended to simplify the implementation of such graph classes.
+ */
+ at SuppressWarnings("serial")
+public abstract class AbstractTypedGraph<V,E> extends AbstractGraph<V, E> 
+{
+	/**
+	 * The edge type for all edges in this graph.
+	 */
+	protected final EdgeType edge_type;
+	
+	/**
+	 * Creates an instance with the specified edge type.
+	 * @param edge_type the type of edges that this graph accepts
+	 */
+	public AbstractTypedGraph(EdgeType edge_type)
+	{
+		this.edge_type = edge_type;
+	}
+	
+	/**
+	 * Returns this graph's edge type.
+	 */
+	public EdgeType getDefaultEdgeType()
+	{
+		return this.edge_type;
+	}
+	
+	/**
+	 * Returns this graph's edge type, or {@code null} if {@code e} is not
+	 * in this graph.
+	 */
+	public EdgeType getEdgeType(E e)
+	{
+		return hasEqualEdgeType(edge_type) ? this.edge_type : null;
+	}
+	
+	/**
+	 * Returns the edge set for this graph if {@code edgeType} matches the 
+	 * edge type for this graph, and an empty set otherwise.
+	 */
+	public Collection<E> getEdges(EdgeType edge_type) 
+	{
+		return hasEqualEdgeType(edge_type) ? this.getEdges() : Collections.<E>emptySet();
+	}
+
+	/**
+	 * Returns the edge count for this graph if {@code edge_type} matches
+	 * the edge type for this graph, and 0 otherwise.
+	 */
+    public int getEdgeCount(EdgeType edge_type)
+    {
+    	return hasEqualEdgeType(edge_type) ? this.getEdgeCount() : 0;
+    }
+    
+    /**
+     * @param edge_type the edge type to compare to this instance's default edge type
+     * @return {@code true} if {@code edge_type} matches the default edge type for 
+     * this graph, and {@code false} otherwise
+     */
+    protected boolean hasEqualEdgeType(EdgeType edge_type)
+    {
+    	return this.edge_type.equals(edge_type);
+    }
+
+    /**
+     * Throws an {@code IllegalArgumentException} if {@code edge_type} does not
+     * match the default edge type for this graph.
+     * @param edge_type the edge type to compare to this instance's default edge type
+     */
+    protected void validateEdgeType(EdgeType edge_type)
+    {
+    	if (!hasEqualEdgeType(edge_type))
+    		throw new IllegalArgumentException("Edge type '" + edge_type + 
+    				"' does not match the default edge type for this graph: '" + 
+    				this.edge_type + "'");
+    }
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DelegateForest.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DelegateForest.java
new file mode 100644
index 0000000..05f4d91
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DelegateForest.java
@@ -0,0 +1,339 @@
+package edu.uci.ics.jung.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.graph.util.TreeUtils;
+
+/**
+ * An implementation of <code>Forest</code> that delegates to a specified <code>DirectedGraph</code>
+ * instance.
+ * @author Tom Nelson
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+ at SuppressWarnings("serial")
+public class DelegateForest<V,E> extends GraphDecorator<V,E> implements Forest<V,E> 
+{
+	/**
+	 * Creates an instance backed by a new {@code DirectedSparseGraph} instance.
+	 */
+	public DelegateForest() {
+		this(new DirectedSparseGraph<V,E>());
+	}
+
+	/**
+	 * Creates an instance backed by the input {@code DirectedGraph}.
+	 * @param delegate the graph to which operations will be delegated
+	 */
+	public DelegateForest(DirectedGraph<V,E> delegate) {
+		super(delegate);
+	}
+
+	/**
+	 * Add an edge to the tree, connecting v1, the parent and v2, the child.
+	 * v1 must already exist in the tree, and v2 must not already exist
+	 * the passed edge must be unique in the tree. Passing an edgeType
+	 * other than EdgeType.DIRECTED may cause an illegal argument exception
+	 * in the delegate graph.
+	 *
+	 * @param e a unique edge to add
+	 * @param v1 the parent node
+	 * @param v2 the child node
+	 * @param edgeType should be EdgeType.DIRECTED
+	 * @return true if this call mutates the underlying graph
+	 * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object, edu.uci.ics.jung.graph.util.EdgeType)
+	 */
+	@Override
+	public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) {
+		if(delegate.getVertices().contains(v1) == false) {
+			throw new IllegalArgumentException("Tree must already contain "+v1);
+		}
+		if(delegate.getVertices().contains(v2)) {
+			throw new IllegalArgumentException("Tree must not already contain "+v2);
+		}
+		return delegate.addEdge(e, v1, v2, edgeType);
+	}
+
+	/**
+	 * Add vertex as a root of the tree
+	 *
+	 * @param vertex the tree root to add
+	 * @return true if this call mutates the underlying graph
+	 * @see edu.uci.ics.jung.graph.Graph#addVertex(java.lang.Object)
+	 */
+	@Override
+	public boolean addVertex(V vertex) {
+		setRoot(vertex);
+		return true;
+	}
+
+	/**
+	 * Removes <code>edge</code> from this tree, and the subtree rooted
+	 * at the child vertex incident to <code>edge</code>.
+	 * (The subtree is removed to ensure that the tree in which the edge
+	 * was found is still a tree rather than a forest.  To change this
+	 * behavior so that the
+	 * @param edge the edge to remove
+	 * @return <code>true</code> iff the tree was modified
+	 * @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object)
+	 */
+	@Override
+	public boolean removeEdge(E edge) {
+	    return removeEdge(edge, true);
+	}
+
+	/**
+	 * Removes <code>edge</code> from this tree.
+	 * If <code>remove_subtree</code> is <code>true</code>, removes
+	 * the subtree rooted at the child vertex incident to <code>edge</code>.
+	 * Otherwise, leaves the subtree intact as a new component tree of this
+	 * forest.
+	 * @param edge the edge to remove
+	 * @param remove_subtree if <code>true</code>, remove the subtree
+	 * @return <code>true</code> iff the tree was modified
+	 */
+	public boolean removeEdge(E edge, boolean remove_subtree)
+	{
+        if (!delegate.containsEdge(edge))
+            return false;
+        V child = getDest(edge);
+        if (remove_subtree)
+            return removeVertex(child);
+        else
+        {
+            delegate.removeEdge(edge);
+            return false;
+        }
+	}
+
+	/**
+	 * Removes <code>vertex</code> from this tree, and the subtree
+	 * rooted at <code>vertex</code>.
+	 * @param vertex the vertex to remove
+	 * @return <code>true</code> iff the tree was modified
+	 * @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object)
+	 */
+	@Override
+	public boolean removeVertex(V vertex) {
+	    return removeVertex(vertex, true);
+	}
+
+	/**
+	 * Removes <code>vertex</code> from this tree.
+     * If <code>remove_subtrees</code> is <code>true</code>, removes
+     * the subtrees rooted at the children of <code>vertex</code>.
+     * Otherwise, leaves these subtrees intact as new component trees of this
+     * forest.
+     * @param vertex the vertex to remove
+	 * @param remove_subtrees if <code>true</code>, remove the subtrees
+	 * rooted at <code>vertex</code>'s children
+	 * @return <code>true</code> iff the tree was modified
+	 */
+	public boolean removeVertex(V vertex, boolean remove_subtrees)
+	{
+        if (!delegate.containsVertex(vertex))
+            return false;
+        if (remove_subtrees)
+            for(V v : new ArrayList<V>(delegate.getSuccessors(vertex)))
+                removeVertex(v, true);
+        return delegate.removeVertex(vertex);
+	}
+
+	/**
+	 * returns an ordered list of the nodes beginning at the root
+	 * and ending at the passed child node, including all intermediate
+	 * nodes.
+	 * @param child the last node in the path from the root
+	 * @return an ordered list of the nodes from root to child
+	 */
+	public List<V> getPath(V child) {
+        if (!delegate.containsVertex(child))
+            return null;
+		List<V> list = new ArrayList<V>();
+		list.add(child);
+		V parent = getParent(child);
+		while(parent != null) {
+			list.add(list.size(), parent);
+			parent = getParent(parent);
+		}
+		return list;
+	}
+
+	public V getParent(V child) {
+        if (!delegate.containsVertex(child))
+            return null;
+		Collection<V> parents = delegate.getPredecessors(child);
+		if(parents.size() > 0) {
+			return parents.iterator().next();
+		}
+		return null;
+	}
+
+	/**
+	 * @return the root of the tree, or null if the tree has > 1 roots
+	 */
+	public V getRoot() {
+		V root = null;
+		for (V v : delegate.getVertices()) {
+			if (delegate.getPredecessorCount(v) == 0) {
+				if (root == null) {
+					root = v;
+				} else {
+					// we've found > 1 root, return null
+					return null;
+				}
+			}
+		}
+		return root;
+	}
+
+	/**
+	 * adds root as a root of the tree
+	 * @param root the initial tree root
+	 */
+	public void setRoot(V root) {
+		delegate.addVertex(root);
+	}
+
+	/**
+	 * removes a node from the tree, causing all descendants of
+	 * the removed node also to be removed
+	 * @param orphan the node to remove
+	 * @return whether this call mutates the underlying graph
+	 */
+	public boolean removeChild(V orphan) {
+		return removeVertex(orphan);
+	}
+
+	/**
+	 * computes and returns the depth of the tree from the
+	 * root to the passed vertex
+	 *
+	 * @param v the node who's depth is computed
+	 * @return the depth to the passed node.
+	 */
+	public int getDepth(V v) {
+		return getPath(v).size();
+	}
+
+	/**
+	 * computes and returns the height of the tree
+	 *
+	 * @return the height
+	 */
+	public int getHeight() {
+		int height = 0;
+		for(V v : getVertices()) {
+			height = Math.max(height, getDepth(v));
+		}
+		return height;
+	}
+
+	/**
+	 * @param v the vertex to test
+	 * @return <code>true</code> if <code>v</code> is neither a leaf
+	 * nor a root
+	 */
+	public boolean isInternal(V v) {
+		return isLeaf(v) == false && isRoot(v) == false;
+	}
+
+	/**
+	 * @param v the vertex to test
+	 * @return {@code true} if {@code v} has no child nodes.
+	 */
+	public boolean isLeaf(V v) {
+		return getChildren(v).size() == 0;
+	}
+
+	/**
+	 * @param v the vertex whose children are to be returned
+	 * @return the children of {@code v}.
+	 */
+	public Collection<V> getChildren(V v) {
+		return delegate.getSuccessors(v);
+	}
+
+	/**
+	 * @param v the vertex to test
+	 * @return {@code true} if {@code v} has no parent node.
+	 */
+	public boolean isRoot(V v) {
+		return getParent(v) == null;
+	}
+
+	@Override
+    public int getIncidentCount(E edge)
+    {
+        return 2;
+    }
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public boolean addEdge(E edge, Collection<? extends V> vertices) {
+		Pair<V> pair = null;
+		if(vertices instanceof Pair) {
+			pair = (Pair<V>)vertices;
+		} else {
+			pair = new Pair<V>(vertices);
+		}
+		return addEdge(edge, pair.getFirst(), pair.getSecond());
+	}
+
+	/**
+	 * @return the root of each tree of this forest as a {@code Collection}.
+	 */
+	public Collection<V> getRoots() {
+		Collection<V> roots = new HashSet<V>();
+		for(V v : delegate.getVertices()) {
+			if(delegate.getPredecessorCount(v) == 0) {
+				roots.add(v);
+			}
+		}
+		return roots;
+	}
+
+	public Collection<Tree<V, E>> getTrees() {
+		Collection<Tree<V,E>> trees = new HashSet<Tree<V,E>>();
+		for(V v : getRoots()) {
+			Tree<V,E> tree = new DelegateTree<V,E>();
+			tree.addVertex(v);
+			TreeUtils.growSubTree(this, tree, v);
+			trees.add(tree);
+		}
+		return trees;
+	}
+
+	/**
+	 * Adds {@code tree} to this graph as an element of this forest.
+	 *
+	 * @param tree the tree to add to this forest as a component
+	 */
+	public void addTree(Tree<V,E> tree) {
+		TreeUtils.addSubTree(this, tree, null, null);
+	}
+
+    public int getChildCount(V vertex)
+    {
+        return delegate.getSuccessorCount(vertex);
+    }
+
+    public Collection<E> getChildEdges(V vertex)
+    {
+        return delegate.getOutEdges(vertex);
+    }
+
+    public E getParentEdge(V vertex)
+    {
+        if (isRoot(vertex))
+            return null;
+        return delegate.getInEdges(vertex).iterator().next();
+    }
+
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DelegateTree.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DelegateTree.java
new file mode 100644
index 0000000..d30aa6e
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DelegateTree.java
@@ -0,0 +1,361 @@
+package edu.uci.ics.jung.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * An implementation of <code>Tree</code> that delegates to
+ * a specified instance of <code>DirectedGraph</code>.
+ * @author Tom Nelson
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+ at SuppressWarnings("serial")
+public class DelegateTree<V,E> extends GraphDecorator<V,E> implements Tree<V,E>
+{
+    /**
+     * @param <V> the vertex type for the graph Supplier
+     * @param <E> the edge type for the graph Supplier
+     * @return a {@code Supplier} that creates an instance of this graph type.
+     */
+    public static final <V,E> Supplier<Tree<V,E>> getFactory() {
+		return new Supplier<Tree<V,E>> () {
+			public Tree<V,E> get() {
+				return new DelegateTree<V,E>(new DirectedSparseMultigraph<V,E>());
+			}
+		};
+	}
+
+	protected V root;
+    protected Map<V, Integer> vertex_depths;
+    
+    /**
+     * Creates an instance.
+     */
+    public DelegateTree() {
+    	this(DirectedSparseMultigraph.<V,E>getFactory());
+    }
+
+	/**
+	 * create an instance with passed values.
+	 * @param graphFactory must create a DirectedGraph to use as a delegate
+	 */
+	public DelegateTree(Supplier<DirectedGraph<V,E>> graphFactory) {
+		super(graphFactory.get());
+        this.vertex_depths = new HashMap<V, Integer>();
+	}
+	
+	/**
+	 * Creates a new <code>DelegateTree</code> which delegates to <code>graph</code>.
+	 * Assumes that <code>graph</code> is already a tree; if it's not, future behavior
+	 * of this instance is undefined.
+	 * @param graph the graph to which this instance will delegate operations.
+	 */
+	public DelegateTree(DirectedGraph<V,E> graph) {
+		super(graph);
+        this.vertex_depths = new HashMap<V, Integer>();
+	}
+	
+	/**
+	 * Add an edge to the tree, connecting v1, the parent and v2, the child.
+	 * v1 must already exist in the tree, and v2 must not already exist
+	 * the passed edge must be unique in the tree. Passing an edgeType
+	 * other than EdgeType.DIRECTED may cause an illegal argument exception 
+	 * in the delegate graph.
+	 * 
+	 * @param e a unique edge to add
+	 * @param v1 the parent node
+	 * @param v2 the child node
+	 * @param edgeType should be EdgeType.DIRECTED
+	 * @return true if this call mutates the underlying graph
+	 * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object, edu.uci.ics.jung.graph.util.EdgeType)
+	 */
+	@Override
+	public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) {
+		return addChild(e, v1, v2, edgeType);
+	}
+
+	/**
+	 * Add an edge to the tree, connecting v1, the parent and v2, the child.
+	 * v1 must already exist in the tree, and v2 must not already exist
+	 * the passed edge must be unique in the tree. 
+	 * 
+	 * @param e a unique edge to add
+	 * @param v1 the parent node
+	 * @param v2 the child node
+	 * @return true if this call mutates the underlying graph
+	 * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object)
+	 */
+	@Override
+	public boolean addEdge(E e, V v1, V v2) {
+		return addChild(e, v1, v2);
+	}
+
+	/**
+	 * Will set the root of the Tree, only if the Tree is empty and the
+	 * root is currently unset.
+	 * 
+	 * @param vertex the tree root to set
+	 * @return true if this call mutates the underlying graph
+	 * @see edu.uci.ics.jung.graph.Graph#addVertex(java.lang.Object)
+	 * @throws UnsupportedOperationException if the root was previously set
+	 */
+	@Override
+	public boolean addVertex(V vertex) {
+		if(root == null) {
+			this.root = vertex;
+            vertex_depths.put(vertex, 0);
+			return delegate.addVertex(vertex);
+		} else {
+			throw new UnsupportedOperationException("Unless you are setting the root, use addChild()");
+		}
+	}
+
+	/**
+	 * remove the passed node, and all nodes that are descendants of the
+	 * passed node.
+	 * @param vertex the vertex to remove
+	 * @return <code>true</code> iff the tree was modified 
+	 * @see edu.uci.ics.jung.graph.Graph#removeVertex(java.lang.Object)
+	 */
+	@Override
+	public boolean removeVertex(V vertex) {
+	    if (!delegate.containsVertex(vertex))
+	        return false;
+		for(V v : getChildren(vertex)) {
+			removeVertex(v);
+            vertex_depths.remove(v);
+		}
+        
+        // recalculate height
+		vertex_depths.remove(vertex);
+		return delegate.removeVertex(vertex);
+	}
+	
+	/**
+	 * add the passed child node as a child of parent.
+	 * parent must exist in the tree, and child must not already exist.
+	 * 
+	 * @param edge the unique edge to connect the parent and child nodes
+	 * @param parent the existing parent to attach the child to
+	 * @param child the new child to add to the tree as a child of parent
+	 * @param edgeType must be EdgeType.DIRECTED or the underlying graph may throw an exception
+	 * @return whether this call mutates the underlying graph
+	 */
+	public boolean addChild(E edge, V parent, V child, EdgeType edgeType) {
+		Collection<V> vertices = delegate.getVertices();
+		if(vertices.contains(parent) == false) {
+			throw new IllegalArgumentException("Tree must already contain parent "+parent);
+		}
+		if(vertices.contains(child)) {
+			throw new IllegalArgumentException("Tree must not already contain child "+child);
+		}
+        vertex_depths.put(child, vertex_depths.get(parent) + 1);
+		return delegate.addEdge(edge, parent, child, edgeType);
+	}
+
+	/**
+	 * add the passed child node as a child of parent.
+	 * parent must exist in the tree, and child must not already exist
+	 * @param edge the unique edge to connect the parent and child nodes
+	 * @param parent the existing parent to attach the child to
+	 * @param child the new child to add to the tree as a child of parent
+	 * @return whether this call mutates the underlying graph
+	 */
+	public boolean addChild(E edge, V parent, V child) {
+		Collection<V> vertices = delegate.getVertices();
+		if(vertices.contains(parent) == false) {
+			throw new IllegalArgumentException("Tree must already contain parent "+parent);
+		}
+		if(vertices.contains(child)) {
+			throw new IllegalArgumentException("Tree must not already contain child "+child);
+		}
+        vertex_depths.put(child, vertex_depths.get(parent) + 1);
+		return delegate.addEdge(edge, parent, child);
+	}
+	
+	/**
+	 * get the number of children of the passed parent node
+	 */
+	public int getChildCount(V parent) {
+	    if (!delegate.containsVertex(parent))
+	        return 0;
+		return getChildren(parent).size();
+	}
+
+	/**
+	 * get the immediate children nodes of the passed parent
+	 */
+	public Collection<V> getChildren(V parent) {
+        if (!delegate.containsVertex(parent))
+            return null;
+		return delegate.getSuccessors(parent);
+	}
+
+	/**
+	 * get the single parent node of the passed child
+	 */
+	public V getParent(V child) {
+        if (!delegate.containsVertex(child))
+            return null;
+		Collection<V> predecessors = delegate.getPredecessors(child);
+		if(predecessors.size() == 0) {
+			return null;
+		}
+		return predecessors.iterator().next();
+	}
+
+	/**
+	 * Returns an ordered list of the nodes beginning at the root
+	 * and ending at {@code vertex}, including all intermediate
+	 * nodes.
+	 * @param vertex the last node in the path from the root
+	 * @return an ordered list of the nodes from root to child
+	 */
+	public List<V> getPath(V vertex) {
+        if (!delegate.containsVertex(vertex))
+            return null;
+		List<V> vertex_to_root = new ArrayList<V>();
+		vertex_to_root.add(vertex);
+		V parent = getParent(vertex);
+		while(parent != null) {
+			vertex_to_root.add(parent);
+			parent = getParent(parent);
+		}
+		// reverse list so that it goes from root to child
+		List<V> root_to_vertex = new ArrayList<V>(vertex_to_root.size());
+		for (int i = vertex_to_root.size() - 1; i >= 0; i--)
+			root_to_vertex.add(vertex_to_root.get(i));
+		return root_to_vertex;
+	}
+
+	/**
+	 * getter for the root of the tree
+	 * @return the root
+	 */
+	public V getRoot() {
+		return root;
+	}
+	
+	/**
+	 * sets the root to the passed value, only if the root is
+	 * previously unset
+	 * @param root the initial tree root
+	 */
+	public void setRoot(V root) {
+		addVertex(root);
+	}
+
+	/**
+	 * removes a node from the tree, causing all descendants of
+	 * the removed node also to be removed
+	 * @param orphan the node to remove
+	 * @return whether this call mutates the underlying graph
+	 */
+	public boolean removeChild(V orphan) {
+		return removeVertex(orphan);
+	}
+
+	/**
+	 * computes and returns the depth of the tree from the
+	 * root to the passed vertex
+	 * 
+	 * @param v the node who's depth is computed
+	 * @return the depth to the passed node.
+	 */
+	public int getDepth(V v) {
+        return this.vertex_depths.get(v);
+	}
+
+	/**
+	 * Computes and returns the height of the tree.
+	 * 
+	 * @return the height
+	 */
+	public int getHeight() {
+		int height = 0;
+		for(V v : getVertices()) {
+			height = Math.max(height, getDepth(v));
+		}
+		return height;
+	}
+
+	/**
+	 * @param v the vertex to test
+	 * @return <code>true</code> if <code>v</code> is neither 
+     * a leaf nor the root of this tree
+	 */
+	public boolean isInternal(V v) {
+	    if (!delegate.containsVertex(v))
+	        return false;
+		return isLeaf(v) == false && isRoot(v) == false;
+	}
+
+	/**
+	 * @param v the vertex to test
+	 * @return <code>true</code> if {@code v} has no children
+	 */
+	public boolean isLeaf(V v) {
+        if (!delegate.containsVertex(v))
+            return false;
+		return getChildren(v).size() == 0;
+	}
+
+	/**
+	 * @param v the vertex to test
+	 * @return <code>true</code> if {@code v} has no parent
+	 */
+	public boolean isRoot(V v) {
+        if (!delegate.containsVertex(v))
+            return false;
+		return getParent(v) == null;
+	}
+
+	@Override
+    public int getIncidentCount(E edge)
+    {
+        if (!delegate.containsEdge(edge))
+            return 0;
+        // all edges in a tree connect exactly 2 vertices
+        return 2;
+    }
+    
+	@SuppressWarnings("unchecked")
+  @Override
+	public boolean addEdge(E edge, Collection<? extends V> vertices) {
+		Pair<V> pair = null;
+		if(vertices instanceof Pair) {
+			pair = (Pair<V>)vertices;
+		} else {
+			pair = new Pair<V>(vertices);
+		}
+		return addEdge(edge, pair.getFirst(), pair.getSecond());
+	}
+	
+	@Override
+	public String toString() {
+		return "Tree of "+delegate.toString();
+	}
+
+	public Collection<Tree<V, E>> getTrees() {
+		return Collections.<Tree<V,E>>singleton(this);
+	}
+
+  public Collection<E> getChildEdges(V vertex) {
+      return getOutEdges(vertex);
+  }
+
+  public E getParentEdge(V vertex) {
+      return getInEdges(vertex).iterator().next();
+  }
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DirectedOrderedSparseMultigraph.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DirectedOrderedSparseMultigraph.java
new file mode 100644
index 0000000..e481640
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DirectedOrderedSparseMultigraph.java
@@ -0,0 +1,113 @@
+/*
+ * Created on Oct 17, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.Pair;
+
+
+/**
+ * An implementation of <code>DirectedGraph</code>, suitable for sparse graphs, 
+ * that orders its vertex and edge collections
+ * according to insertion time.
+ */
+ at SuppressWarnings("serial")
+public class DirectedOrderedSparseMultigraph<V,E> 
+    extends DirectedSparseMultigraph<V,E>
+    implements DirectedGraph<V,E>, MultiGraph<V,E> 
+{
+    /**
+     * @param <V> the vertex type for the graph Supplier
+     * @param <E> the edge type for the graph Supplier
+     * @return a {@code Supplier} that creates an instance of this graph type.
+     */
+	public static <V,E> Supplier<DirectedGraph<V,E>> getFactory() {
+		return new Supplier<DirectedGraph<V,E>> () {
+			public DirectedGraph<V,E> get() {
+				return new DirectedOrderedSparseMultigraph<V,E>();
+			}
+		};
+	}
+
+    /**
+     * Creates a new instance.
+     */
+    public DirectedOrderedSparseMultigraph() {
+        vertices = new LinkedHashMap<V, Pair<Set<E>>>();
+        edges = new LinkedHashMap<E, Pair<V>>();
+    }
+    
+    @Override
+    public boolean addVertex(V vertex) {
+    	if(vertex == null) {
+    		throw new IllegalArgumentException("vertex may not be null");
+    	}
+        if (!containsVertex(vertex)) {
+            vertices.put(vertex, new Pair<Set<E>>(new LinkedHashSet<E>(), new LinkedHashSet<E>()));
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public Collection<V> getPredecessors(V vertex) {
+        if (!containsVertex(vertex)) 
+            return null;
+        Set<V> preds = new LinkedHashSet<V>();
+        for (E edge : getIncoming_internal(vertex))
+            preds.add(this.getSource(edge));
+        
+        return Collections.unmodifiableCollection(preds);
+    }
+
+    @Override
+    public Collection<V> getSuccessors(V vertex) {
+        if (!containsVertex(vertex)) 
+            return null;
+        Set<V> succs = new LinkedHashSet<V>();
+        for (E edge : getOutgoing_internal(vertex))
+            succs.add(this.getDest(edge));
+        
+        return Collections.unmodifiableCollection(succs);
+    }
+
+    @Override
+    public Collection<V> getNeighbors(V vertex) {
+        if (!containsVertex(vertex)) 
+            return null;
+        Collection<V> neighbors = new LinkedHashSet<V>();
+        for (E edge : getIncoming_internal(vertex))
+            neighbors.add(this.getSource(edge));
+        for (E edge : getOutgoing_internal(vertex))
+            neighbors.add(this.getDest(edge));
+        return Collections.unmodifiableCollection(neighbors);
+    }
+
+    @Override
+    public Collection<E> getIncidentEdges(V vertex) {
+        if (!containsVertex(vertex)) 
+            return null;
+        Collection<E> incident = new LinkedHashSet<E>();
+        incident.addAll(getIncoming_internal(vertex));
+        incident.addAll(getOutgoing_internal(vertex));
+        return incident;
+    }
+
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DirectedSparseGraph.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DirectedSparseGraph.java
new file mode 100644
index 0000000..c6476a7
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DirectedSparseGraph.java
@@ -0,0 +1,289 @@
+/*
+ * Created on Mar 26, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * An implementation of <code>DirectedGraph</code> suitable for sparse graphs.
+ */
+ at SuppressWarnings("serial")
+public class DirectedSparseGraph<V,E> extends AbstractTypedGraph<V, E> implements
+        DirectedGraph<V, E>
+{
+    /**
+     * @param <V> the vertex type for the graph Supplier
+     * @param <E> the edge type for the graph Supplier
+     * @return a {@code Supplier} that creates an instance of this graph type.
+     */
+   public static final <V,E> Supplier<DirectedGraph<V,E>> getFactory() {
+        return new Supplier<DirectedGraph<V,E>> () {
+            public DirectedGraph<V,E> get() {
+                return new DirectedSparseGraph<V,E>();
+            }
+        };
+    }
+
+    protected Map<V, Pair<Map<V,E>>> vertices;  // Map of vertices to Pair of adjacency maps {incoming, outgoing} 
+                                                // of neighboring vertices to incident edges
+    protected Map<E, Pair<V>> edges;            // Map of edges to incident vertex pairs
+
+    /**
+     * Creates an instance.
+     */
+    public DirectedSparseGraph() 
+    {
+    	super(EdgeType.DIRECTED);
+        vertices = new HashMap<V, Pair<Map<V,E>>>();
+        edges = new HashMap<E, Pair<V>>();
+    }
+    
+    @Override
+    public boolean addEdge(E edge, Pair<? extends V> endpoints, EdgeType edgeType)
+    {
+    	this.validateEdgeType(edgeType);
+        Pair<V> new_endpoints = getValidatedEndpoints(edge, endpoints);
+        if (new_endpoints == null)
+            return false;
+        
+        V source = new_endpoints.getFirst();
+        V dest = new_endpoints.getSecond();
+        
+        if (findEdge(source, dest) != null)
+            return false;
+        
+        edges.put(edge, new_endpoints);
+
+        if (!vertices.containsKey(source))
+            this.addVertex(source);
+        
+        if (!vertices.containsKey(dest))
+            this.addVertex(dest);
+        
+        // map source of this edge to <dest, edge> and vice versa
+        vertices.get(source).getSecond().put(dest, edge);
+        vertices.get(dest).getFirst().put(source, edge);
+
+        return true;
+    }
+
+    @Override
+    public E findEdge(V v1, V v2)
+    {
+        if (!containsVertex(v1) || !containsVertex(v2))
+            return null;
+        return vertices.get(v1).getSecond().get(v2);
+    }
+
+    @Override
+    public Collection<E> findEdgeSet(V v1, V v2)
+    {
+        if (!containsVertex(v1) || !containsVertex(v2))
+            return null;
+        ArrayList<E> edge_collection = new ArrayList<E>(1);
+        E e = findEdge(v1, v2);
+        if (e == null)
+            return edge_collection;
+        edge_collection.add(e);
+        return edge_collection;
+    }
+    
+    protected Collection<E> getIncoming_internal(V vertex)
+    {
+        return vertices.get(vertex).getFirst().values();
+    }
+    
+    protected Collection<E> getOutgoing_internal(V vertex)
+    {
+        return vertices.get(vertex).getSecond().values();
+    }
+    
+    protected Collection<V> getPreds_internal(V vertex)
+    {
+        return vertices.get(vertex).getFirst().keySet();
+    }
+    
+    protected Collection<V> getSuccs_internal(V vertex)
+    {
+        return vertices.get(vertex).getSecond().keySet();
+    }
+    
+    public Collection<E> getInEdges(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        return Collections.unmodifiableCollection(getIncoming_internal(vertex));
+    }
+
+    public Collection<E> getOutEdges(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        return Collections.unmodifiableCollection(getOutgoing_internal(vertex));
+    }
+
+    public Collection<V> getPredecessors(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        return Collections.unmodifiableCollection(getPreds_internal(vertex));
+    }
+
+    public Collection<V> getSuccessors(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        return Collections.unmodifiableCollection(getSuccs_internal(vertex));
+    }
+
+    public Pair<V> getEndpoints(E edge)
+    {
+        if (!containsEdge(edge))
+            return null;
+        return edges.get(edge);
+    }
+
+    public V getSource(E directed_edge)
+    {
+        if (!containsEdge(directed_edge))
+            return null;
+        return edges.get(directed_edge).getFirst();
+    }
+
+    public V getDest(E directed_edge)
+    {
+        if (!containsEdge(directed_edge))
+            return null;
+        return edges.get(directed_edge).getSecond();
+    }
+
+    public boolean isSource(V vertex, E edge)
+    {
+        if (!containsEdge(edge) || !containsVertex(vertex))
+            return false;
+        return vertex.equals(this.getEndpoints(edge).getFirst());
+    }
+
+    public boolean isDest(V vertex, E edge)
+    {
+        if (!containsEdge(edge) || !containsVertex(vertex))
+            return false;
+        return vertex.equals(this.getEndpoints(edge).getSecond());
+    }
+
+    public Collection<E> getEdges()
+    {
+        return Collections.unmodifiableCollection(edges.keySet());
+    }
+
+    public Collection<V> getVertices()
+    {
+        return Collections.unmodifiableCollection(vertices.keySet());
+    }
+
+    public boolean containsVertex(V vertex)
+    {
+        return vertices.containsKey(vertex);
+    }
+
+    public boolean containsEdge(E edge)
+    {
+        return edges.containsKey(edge);
+    }
+
+    public int getEdgeCount()
+    {
+        return edges.size();
+    }
+
+    public int getVertexCount()
+    {
+        return vertices.size();
+    }
+
+    public Collection<V> getNeighbors(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        
+        Collection<V> neighbors = new HashSet<V>();
+        neighbors.addAll(getPreds_internal(vertex));
+        neighbors.addAll(getSuccs_internal(vertex));
+        return Collections.unmodifiableCollection(neighbors);
+    }
+
+    public Collection<E> getIncidentEdges(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        
+        Collection<E> incident_edges = new HashSet<E>();
+        incident_edges.addAll(getIncoming_internal(vertex));
+        incident_edges.addAll(getOutgoing_internal(vertex));
+        return Collections.unmodifiableCollection(incident_edges);
+    }
+
+    public boolean addVertex(V vertex)
+    {
+        if(vertex == null) {
+            throw new IllegalArgumentException("vertex may not be null");
+        }
+        if (!containsVertex(vertex)) {
+            vertices.put(vertex, new Pair<Map<V,E>>(new HashMap<V,E>(), new HashMap<V,E>()));
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public boolean removeVertex(V vertex) {
+        if (!containsVertex(vertex))
+            return false;
+        
+        // copy to avoid concurrent modification in removeEdge
+        ArrayList<E> incident = new ArrayList<E>(getIncoming_internal(vertex));
+        incident.addAll(getOutgoing_internal(vertex));
+        
+        for (E edge : incident)
+            removeEdge(edge);
+        
+        vertices.remove(vertex);
+        
+        return true;
+    }
+    
+    public boolean removeEdge(E edge) {
+        if (!containsEdge(edge))
+            return false;
+        
+        Pair<V> endpoints = this.getEndpoints(edge);
+        V source = endpoints.getFirst();
+        V dest = endpoints.getSecond();
+        
+        // remove vertices from each others' adjacency maps
+        vertices.get(source).getSecond().remove(dest);
+        vertices.get(dest).getFirst().remove(source);
+        
+        edges.remove(edge);
+        return true;
+    }
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DirectedSparseMultigraph.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DirectedSparseMultigraph.java
new file mode 100644
index 0000000..5fb52c0
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/DirectedSparseMultigraph.java
@@ -0,0 +1,262 @@
+/*
+ * Created on Oct 17, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+
+/**
+ * An implementation of <code>DirectedGraph</code>, suitable for sparse graphs,
+ * that permits parallel edges.
+ */
+ at SuppressWarnings("serial")
+public class DirectedSparseMultigraph<V,E> 
+    extends AbstractTypedGraph<V,E>
+    implements DirectedGraph<V,E>, MultiGraph<V,E> {
+
+    /**
+     * @param <V> the vertex type for the graph Supplier
+     * @param <E> the edge type for the graph Supplier
+     * @return a {@code Supplier} that creates an instance of this graph type.
+     */
+	public static <V,E> Supplier<DirectedGraph<V,E>> getFactory() {
+		return new Supplier<DirectedGraph<V,E>> () {
+			public DirectedGraph<V,E> get() {
+				return new DirectedSparseMultigraph<V,E>();
+			}
+		};
+	}
+
+	protected Map<V, Pair<Set<E>>> vertices; // Map of vertices to Pair of adjacency sets {incoming, outgoing}
+    protected Map<E, Pair<V>> edges;            // Map of edges to incident vertex pairs
+
+    /**
+     * Creates a new instance.
+     */
+    public DirectedSparseMultigraph() {
+    	super(EdgeType.DIRECTED);
+        vertices = new HashMap<V, Pair<Set<E>>>();
+        edges = new HashMap<E, Pair<V>>();
+    }
+    
+    public Collection<E> getEdges() {
+        return Collections.unmodifiableCollection(edges.keySet());
+    }
+
+    public Collection<V> getVertices() {
+        return Collections.unmodifiableCollection(vertices.keySet());
+    }
+
+    public boolean containsVertex(V vertex) {
+    	return vertices.keySet().contains(vertex);
+    }
+    
+    public boolean containsEdge(E edge) {
+    	return edges.keySet().contains(edge);
+    }
+
+    protected Collection<E> getIncoming_internal(V vertex)
+    {
+        return vertices.get(vertex).getFirst();
+    }
+    
+    protected Collection<E> getOutgoing_internal(V vertex)
+    {
+        return vertices.get(vertex).getSecond();
+    }
+    
+    public boolean addVertex(V vertex) {
+    	if(vertex == null) {
+    		throw new IllegalArgumentException("vertex may not be null");
+    	}
+        if (!containsVertex(vertex)) {
+            vertices.put(vertex, new Pair<Set<E>>(new HashSet<E>(), new HashSet<E>()));
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public boolean removeVertex(V vertex) {
+        if (!containsVertex(vertex))
+            return false;
+        
+        // copy to avoid concurrent modification in removeEdge
+        Set<E> incident = new HashSet<E>(getIncoming_internal(vertex));
+        incident.addAll(getOutgoing_internal(vertex));
+        
+        for (E edge : incident)
+            removeEdge(edge);
+        
+        vertices.remove(vertex);
+        
+        return true;
+    }
+    
+    public boolean removeEdge(E edge) {
+        if (!containsEdge(edge))
+            return false;
+        
+        Pair<V> endpoints = this.getEndpoints(edge);
+        V source = endpoints.getFirst();
+        V dest = endpoints.getSecond();
+        
+        // remove edge from incident vertices' adjacency sets
+        getOutgoing_internal(source).remove(edge);
+        getIncoming_internal(dest).remove(edge);
+        
+        edges.remove(edge);
+        return true;
+    }
+
+    
+    public Collection<E> getInEdges(V vertex) {
+        if (!containsVertex(vertex))
+            return null;
+
+        return Collections.unmodifiableCollection(getIncoming_internal(vertex));
+    }
+
+    public Collection<E> getOutEdges(V vertex) {
+        if (!containsVertex(vertex))
+            return null;
+        
+        return Collections.unmodifiableCollection(getOutgoing_internal(vertex));
+    }
+
+    public Collection<V> getPredecessors(V vertex) {
+        if (!containsVertex(vertex))
+            return null;
+
+        Set<V> preds = new HashSet<V>();
+        for (E edge : getIncoming_internal(vertex))
+            preds.add(this.getSource(edge));
+        
+        return Collections.unmodifiableCollection(preds);
+    }
+
+    public Collection<V> getSuccessors(V vertex) {
+        if (!containsVertex(vertex))
+            return null;
+        
+        Set<V> succs = new HashSet<V>();
+        for (E edge : getOutgoing_internal(vertex))
+            succs.add(this.getDest(edge));
+        
+        return Collections.unmodifiableCollection(succs);
+    }
+
+    public Collection<V> getNeighbors(V vertex) {
+        if (!containsVertex(vertex))
+            return null;
+        
+        Collection<V> neighbors = new HashSet<V>();
+        for (E edge : getIncoming_internal(vertex))
+            neighbors.add(this.getSource(edge));
+        for (E edge : getOutgoing_internal(vertex))
+            neighbors.add(this.getDest(edge));
+        return Collections.unmodifiableCollection(neighbors);
+    }
+
+    public Collection<E> getIncidentEdges(V vertex) {
+        if (!containsVertex(vertex))
+            return null;
+        
+        Collection<E> incident = new HashSet<E>();
+        incident.addAll(getIncoming_internal(vertex));
+        incident.addAll(getOutgoing_internal(vertex));
+        return incident;
+    }
+
+    @Override
+    public E findEdge(V v1, V v2) {
+        if (!containsVertex(v1) || !containsVertex(v2))
+            return null;
+        for (E edge : getOutgoing_internal(v1))
+            if (this.getDest(edge).equals(v2))
+                return edge;
+        
+        return null;
+    }
+    
+	@Override
+  public boolean addEdge(E edge, Pair<? extends V> endpoints, EdgeType edgeType) 
+	{
+		this.validateEdgeType(edgeType);
+        Pair<V> new_endpoints = getValidatedEndpoints(edge, endpoints);
+        if (new_endpoints == null)
+            return false;
+        
+        edges.put(edge, new_endpoints);
+        
+        V source = new_endpoints.getFirst();
+        V dest = new_endpoints.getSecond();
+
+        if (!containsVertex(source))
+            this.addVertex(source);
+        
+        if (!containsVertex(dest))
+            this.addVertex(dest);
+        
+        getIncoming_internal(dest).add(edge);
+        getOutgoing_internal(source).add(edge);
+
+        return true;
+	}
+
+    
+    public V getSource(E edge) {
+        if (!containsEdge(edge))
+            return null;
+        return this.getEndpoints(edge).getFirst();
+    }
+
+    public V getDest(E edge) {
+        if (!containsEdge(edge))
+            return null;
+        return this.getEndpoints(edge).getSecond();
+    }
+
+    public boolean isSource(V vertex, E edge) {
+        if (!containsEdge(edge) || !containsVertex(vertex))
+            return false;
+        return vertex.equals(this.getEndpoints(edge).getFirst());
+    }
+
+    public boolean isDest(V vertex, E edge) {
+        if (!containsEdge(edge) || !containsVertex(vertex))
+            return false;
+        return vertex.equals(this.getEndpoints(edge).getSecond());
+    }
+
+    public Pair<V> getEndpoints(E edge) {
+        return edges.get(edge);
+    }
+
+	public int getEdgeCount() {
+		return edges.size();
+	}
+
+	public int getVertexCount() {
+		return vertices.size();
+	}
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/OrderedKAryTree.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/OrderedKAryTree.java
new file mode 100644
index 0000000..4cf168c
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/OrderedKAryTree.java
@@ -0,0 +1,807 @@
+/*
+ * Created on May 8, 2008
+ *
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * An implementation of <code>Tree</code> in which each vertex has
+ * ≤ k children.  The value of 'k' is specified by the constructor
+ * parameter.  A specific child (edge) can be retrieved directly by specifying the
+ * index at which the child is located.  By default, new (child) vertices
+ * are added at the lowest index available, if no index is specified.
+ * 
+ */
+ at SuppressWarnings("serial")
+public class OrderedKAryTree<V, E> extends AbstractTypedGraph<V, E> implements Tree<V, E> 
+{
+    protected Map<E, Pair<V>> edge_vpairs;
+    protected Map<V, VertexData> vertex_data;
+    protected int height;
+    protected V root;
+    protected int order;
+    
+    /**
+     * @param <V> the vertex type for the graph Supplier
+     * @param <E> the edge type for the graph Supplier
+     * @param order the maximum number of children ("k") that any vertex can have
+     * @return a {@code Supplier} that creates an instance of this graph type.
+     */
+    public static <V,E> Supplier<DirectedGraph<V,E>> getFactory(final int order) {
+        return new Supplier<DirectedGraph<V,E>> () {
+            public DirectedGraph<V,E> get() {
+                return new OrderedKAryTree<V,E>(order);
+            }
+        };
+    }
+    
+    /**
+     * Creates a new instance with the specified order (maximum number of children).
+     * @param order the maximum number of children ("k") that any vertex can have
+     */
+    public OrderedKAryTree(int order)
+    {
+    	super(EdgeType.DIRECTED);
+    	this.order = order;
+    	this.height = -1;
+    	this.edge_vpairs = new HashMap<E, Pair<V>>();
+    	this.vertex_data = new HashMap<V, VertexData>();
+    }
+  
+    /**
+     * @param vertex the vertex whose number of children is to be returned
+     * @return the number of children that {@code vertex} has
+     * @see edu.uci.ics.jung.graph.Tree#getChildCount(java.lang.Object)
+     */
+    public int getChildCount(V vertex) {
+        if (!containsVertex(vertex)) 
+            return 0;
+        List<E> edges = vertex_data.get(vertex).child_edges;
+        if (edges == null)
+        	return 0;
+        int count = 0;
+        for (E edge : edges)
+            count += edge == null ? 0 : 1;
+    
+        return count;
+    }
+  
+    /**
+     * @param vertex the vertex whose child edge is to be returned
+     * @param index the index of the edge to be returned
+     * @return the child edge of {@code vertex} at index {@code index}, that is, 
+     *     its <i>i</i>th child edge.
+     */
+    public E getChildEdge(V vertex, int index) 
+    {
+        if (!containsVertex(vertex)) 
+        	return null;
+        List<E> edges = vertex_data.get(vertex).child_edges;
+        if (edges == null)
+        	return null;
+        return edges.get(index);
+    }
+
+    /**
+     * @see edu.uci.ics.jung.graph.Tree#getChildEdges(java.lang.Object)
+     */
+    public Collection<E> getChildEdges(V vertex) 
+    {
+        if (!containsVertex(vertex)) 
+        	return null;
+        List<E> edges = vertex_data.get(vertex).child_edges;
+        return edges == null ? Collections.<E>emptySet() : 
+        	new ImmutableList.Builder<E>().addAll(edges).build();
+    }
+  
+    /**
+     * Returns an ordered list of {@code vertex}'s child vertices.
+     * If there is no child in position i, then the list will contain
+     * {@code null} in position i.  If {@code vertex} has no children
+     * then the empty set will be returned.
+     * @see edu.uci.ics.jung.graph.Tree#getChildren(java.lang.Object)
+     */
+    public Collection<V> getChildren(V vertex) 
+    {
+        if (!containsVertex(vertex)) 
+            return null;
+        List<E> edges = vertex_data.get(vertex).child_edges;
+        if (edges == null)
+        	return Collections.emptySet();
+        Collection<V> children = new ArrayList<V>(order);
+        for (E edge : edges)
+            children.add(this.getOpposite(vertex, edge));
+        return new ImmutableList.Builder<V>().addAll(children).build();
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Tree#getDepth(java.lang.Object)
+     * @return the depth of the vertex in this tree, or -1 if the vertex is
+     * not present in this tree
+     */
+    public int getDepth(V vertex) 
+    {
+        if (!containsVertex(vertex))
+            return -1;
+        return vertex_data.get(vertex).depth;
+    }
+  
+    /**
+     * Returns the height of the tree, or -1 if the tree is empty.
+     * @see edu.uci.ics.jung.graph.Tree#getHeight()
+     */
+    public int getHeight() 
+    {
+        return height;
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Tree#getParent(java.lang.Object)
+     */
+    public V getParent(V vertex) 
+    {
+        if (!containsVertex(vertex))
+            return null;
+        else if (vertex.equals(root))
+            return null;
+        return edge_vpairs.get(vertex_data.get(vertex).parent_edge).getFirst();
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Tree#getParentEdge(java.lang.Object)
+     */
+    public E getParentEdge(V vertex) 
+    {
+        if (!containsVertex(vertex))
+            return null;
+        return vertex_data.get(vertex).parent_edge;
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Tree#getRoot()
+     */
+    public V getRoot() 
+    {
+        return root;
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Forest#getTrees()
+     */
+    public Collection<Tree<V, E>> getTrees() 
+    {
+        Collection<Tree<V, E>> forest = new ArrayList<Tree<V, E>>(1);
+        forest.add(this);
+        return forest;
+    }
+  
+    /**
+     * Adds the specified {@code child} vertex and edge {@code e} to the graph
+     * with the specified parent vertex {@code parent}.  If {@code index} is 
+     * greater than or equal to 0, then the child is placed at position
+     * {@code index}; if it is less than 0, the child is placed at the lowest
+     * available position; if it is greater than or equal to the order of this
+     * tree, an exception is thrown.
+     * 
+     * @param e the edge to add
+     * @param parent the source of the edge to be added
+     * @param child the destination of the edge to be added
+     * @param index the position at which e is to be added as a child of {@code parent}
+     * @return {@code true} if the graph has been modified
+     * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object)
+     */
+	public boolean addEdge(E e, V parent, V child, int index) 
+    {
+        if (e == null || child == null || parent == null)
+            throw new IllegalArgumentException("Inputs may not be null");
+    	if (!containsVertex(parent))
+    		throw new IllegalArgumentException("Tree must already " +
+    				"include parent: " + parent);
+    	if (containsVertex(child))
+    		throw new IllegalArgumentException("Tree must not already " +
+    				"include child: " + child);
+		if (parent.equals(child))
+			throw new IllegalArgumentException("Input vertices must be distinct");
+		if (index < 0 || index >= order)
+		    throw new IllegalArgumentException("'index' must be in [0, [order-1]]");
+    	
+    	Pair<V> endpoints = new Pair<V>(parent, child);
+    	if (containsEdge(e))
+    		if (!endpoints.equals(edge_vpairs.get(e)))
+    			throw new IllegalArgumentException("Tree already includes edge" + 
+    					e + " with different endpoints " + edge_vpairs.get(e));
+    		else
+    			return false;
+
+    	VertexData parent_data = vertex_data.get(parent);
+    	List<E> outedges = parent_data.child_edges;
+    	
+    	if (outedges == null)
+    	    outedges = new ArrayList<E>(this.order);
+
+    	boolean edge_placed = false;
+    	if (index >= 0)
+    		if (outedges.get(index) != null)
+        		throw new IllegalArgumentException("Parent " + parent + 
+        				" already has a child at index " + index + " in this tree");
+    		else
+    			outedges.set(index, e);
+    	for (int i = 0; i < order; i++)
+    	{
+    		if (outedges.get(i) == null)
+    		{
+    			outedges.set(i, e);
+    			edge_placed = true;
+    			break;
+    		}
+    	}
+    	if (!edge_placed)
+    		throw new IllegalArgumentException("Parent " + parent + " already" +
+    				" has " + order + " children in this tree");
+    	
+    	// initialize VertexData for child; leave child's child_edges null for now
+    	VertexData child_data = new VertexData(e, parent_data.depth + 1);
+    	vertex_data.put(child, child_data);
+    	
+    	height = child_data.depth > height ? child_data.depth : height;
+    	edge_vpairs.put(e, endpoints);
+    	
+    	return true;
+    }
+
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object)
+     */
+	@Override
+    public boolean addEdge(E e, V parent, V child)
+	{
+		return addEdge(e, parent, child, -1);
+	}
+
+    
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object, edu.uci.ics.jung.graph.util.EdgeType)
+     */
+    @Override
+    public boolean addEdge(E e, V v1, V v2, EdgeType edge_type) 
+    {
+    	this.validateEdgeType(edge_type);
+    	return addEdge(e, v1, v2);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#getDest(java.lang.Object)
+     */
+    public V getDest(E directed_edge) 
+    {
+        if (!containsEdge(directed_edge))
+            return null;
+        return edge_vpairs.get(directed_edge).getSecond();
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#getEndpoints(java.lang.Object)
+     */
+    public Pair<V> getEndpoints(E edge) 
+    {
+        if (!containsEdge(edge))
+            return null;
+        return edge_vpairs.get(edge);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#getInEdges(java.lang.Object)
+     */
+    public Collection<E> getInEdges(V vertex) 
+    {
+        if (!containsVertex(vertex))
+            return null;
+        else if (vertex.equals(root))
+            return Collections.emptySet();
+        else
+            return Collections.singleton(getParentEdge(vertex));
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#getOpposite(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public V getOpposite(V vertex, E edge) 
+    {
+        if (!containsVertex(vertex) || !containsEdge(edge))
+            return null;
+        Pair<V> endpoints = edge_vpairs.get(edge);
+        V v1 = endpoints.getFirst();
+        V v2 = endpoints.getSecond();
+        return v1.equals(vertex) ? v2 : v1;
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#getOutEdges(java.lang.Object)
+     */
+    public Collection<E> getOutEdges(V vertex) 
+    {
+        return getChildEdges(vertex);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#getPredecessorCount(java.lang.Object)
+     * @return 0 if <code>vertex</code> is the root, -1 if the vertex is 
+     * not an element of this tree, and 1 otherwise
+     */
+    @Override
+    public int getPredecessorCount(V vertex) 
+    {
+        if (!containsVertex(vertex))
+            return -1;
+        return vertex.equals(root) ? 0 : 1;
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#getPredecessors(java.lang.Object)
+     */
+    public Collection<V> getPredecessors(V vertex) 
+    {
+        if (!containsVertex(vertex))
+            return null;
+        if (vertex.equals(root))
+            return Collections.emptySet();
+        return Collections.singleton(getParent(vertex));
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#getSource(java.lang.Object)
+     */
+    public V getSource(E directed_edge) 
+    {
+        if (!containsEdge(directed_edge))
+            return null;
+        return edge_vpairs.get(directed_edge).getFirst();
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#getSuccessorCount(java.lang.Object)
+     */
+    @Override
+    public int getSuccessorCount(V vertex) 
+    {
+        return getChildCount(vertex);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#getSuccessors(java.lang.Object)
+     */
+    public Collection<V> getSuccessors(V vertex) 
+    {
+        return getChildren(vertex);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#inDegree(java.lang.Object)
+     */
+    @Override
+    public int inDegree(V vertex) 
+    {
+        if (!containsVertex(vertex))
+            return 0;
+        if (vertex.equals(root))
+            return 0;
+        return 1;
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#isDest(java.lang.Object, java.lang.Object)
+     */
+    public boolean isDest(V vertex, E edge) 
+    {
+        if (!containsEdge(edge) || !containsVertex(vertex))
+            return false;
+        return edge_vpairs.get(edge).getSecond().equals(vertex);
+    }
+  
+    /**
+     * Returns <code>true</code> if <code>vertex</code> is a leaf of this tree,
+     * i.e., if it has no children.
+     * @param vertex the vertex to be queried
+     * @return <code>true</code> if <code>outDegree(vertex)==0</code>
+     */
+    public boolean isLeaf(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return false;
+        return outDegree(vertex) == 0;
+    }
+    
+    /**
+     * Returns true iff <code>v1</code> is the parent of <code>v2</code>.
+     * Note that if <code>v2</code> is the root and <code>v1</code> is <code>null</code>,
+     * this method returns <code>true</code>.
+     * 
+     * @see edu.uci.ics.jung.graph.Graph#isPredecessor(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public boolean isPredecessor(V v1, V v2) 
+    {
+        if (!containsVertex(v2))
+            return false;
+        return getParent(v2).equals(v1);
+    }
+  
+    /**
+     * Returns <code>true</code> if <code>vertex</code> is a leaf of this tree,
+     * i.e., if it has no children.
+     * @param vertex the vertex to be queried
+     * @return <code>true</code> if <code>outDegree(vertex)==0</code>
+     */
+    public boolean isRoot(V vertex)
+    {
+        if (root == null)
+            return false;
+        return root.equals(vertex);
+    }
+    
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#isSource(java.lang.Object, java.lang.Object)
+     */
+    public boolean isSource(V vertex, E edge) 
+    {
+        if (!containsEdge(edge) || !containsVertex(vertex))
+            return false;
+        return edge_vpairs.get(edge).getFirst().equals(vertex);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#isSuccessor(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public boolean isSuccessor(V v1, V v2) 
+    {
+        if (!containsVertex(v2))
+            return false;
+        if (containsVertex(v1))
+            return getParent(v1).equals(v2);
+        return isLeaf(v2) && v1 == null;
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Graph#outDegree(java.lang.Object)
+     */
+    @Override
+    public int outDegree(V vertex) 
+    {
+        if (!containsVertex(vertex))
+            return 0;
+        List<E> out_edges = vertex_data.get(vertex).child_edges;
+        if (out_edges == null)
+        	return 0;
+        int degree = 0;
+        for (E e : out_edges)
+        	degree += (e == null) ? 0 : 1;
+        return degree;
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#addEdge(java.lang.Object, java.util.Collection)
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+	public boolean addEdge(E edge, Collection<? extends V> vertices, EdgeType edge_type) 
+    {
+        if (edge == null || vertices == null)
+            throw new IllegalArgumentException("inputs may not be null");
+        if (vertices.size() != 2)
+            throw new IllegalArgumentException("'vertices' must contain " +
+            		"exactly 2 distinct vertices");
+    	this.validateEdgeType(edge_type);
+		Pair<V> endpoints;
+		if (vertices instanceof Pair)
+			endpoints = (Pair<V>)vertices;
+		else
+			endpoints = new Pair<V>(vertices);
+		V v1 = endpoints.getFirst();
+		V v2 = endpoints.getSecond();
+		if (v1.equals(v2))
+			throw new IllegalArgumentException("Input vertices must be distinct");
+		return addEdge(edge, v1, v2);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#addVertex(java.lang.Object)
+     */
+    public boolean addVertex(V vertex) 
+    {
+		if(root == null) 
+		{
+			this.root = vertex;
+			vertex_data.put(vertex, new VertexData(null, 0));
+			this.height = 0;
+			return true;
+		} 
+		else 
+		{
+			throw new UnsupportedOperationException("Unless you are setting " +
+					"the root, use addEdge() or addChild()");
+		}
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#isIncident(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public boolean isIncident(V vertex, E edge) 
+    {
+        if (!containsVertex(vertex) || !containsEdge(edge))
+            return false;
+        return edge_vpairs.get(edge).contains(vertex);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#isNeighbor(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public boolean isNeighbor(V v1, V v2) 
+    {
+    	if (!containsVertex(v1) || !containsVertex(v2))
+    		return false;
+    	return getNeighbors(v1).contains(v2);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#containsEdge(java.lang.Object)
+     */
+    public boolean containsEdge(E edge) 
+    {
+    	return edge_vpairs.containsKey(edge);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#containsVertex(java.lang.Object)
+     */
+    public boolean containsVertex(V vertex) 
+    {
+    	return vertex_data.containsKey(vertex);
+    }
+  
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#findEdge(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public E findEdge(V v1, V v2) 
+    {
+        if (!containsVertex(v1) || !containsVertex(v2))
+            return null;
+    	VertexData v1_data = vertex_data.get(v1);
+    	if (edge_vpairs.get(v1_data.parent_edge).getFirst().equals(v2))
+    		return v1_data.parent_edge;
+    	List<E> edges = v1_data.child_edges;
+    	if (edges == null)
+    		return null;
+    	for (E edge : edges)
+    		if (edge != null && edge_vpairs.get(edge).getSecond().equals(v2))
+    			return edge;
+    	return null;
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#findEdgeSet(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public Collection<E> findEdgeSet(V v1, V v2) 
+    {
+    	E edge = findEdge(v1, v2);
+    	if (edge == null)
+    		return Collections.emptySet();
+    	else
+    		return Collections.singleton(edge);
+    }
+  
+    /**
+     * Returns the child of <code>vertex</code> at position <code>index</code> 
+     * in this tree, or <code>null</code> if it has no child at that position.
+     * @param vertex the vertex to query
+     * @param index the index of the child to return
+     * @return the child of <code>vertex</code> at position <code>index</code> 
+     * in this tree, or <code>null</code> if it has no child at that position
+     * @throws ArrayIndexOutOfBoundsException if <code>index</code> is not in 
+     * the range {@code [0, order-1]}
+     */
+    public V getChild(V vertex, int index)
+    {
+        if (index < 0 || index >= order)
+            throw new ArrayIndexOutOfBoundsException(index + " is not in [0, order-1]");
+        if (!containsVertex(vertex))
+            return null;
+        List<E> edges = vertex_data.get(vertex).child_edges;
+        if (edges == null)
+        	return null;
+        E edge = edges.get(index);
+        return edge == null ? null : edge_vpairs.get(edge).getSecond();
+    }
+    
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount()
+     */
+    public int getEdgeCount() 
+    {
+    	return edge_vpairs.size();
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#getEdges()
+     */
+    public Collection<E> getEdges() 
+    {
+    	return new ImmutableSet.Builder<E>().addAll(edge_vpairs.keySet()).build();
+    	//CollectionUtils.unmodifiableCollection(edge_vpairs.keySet());
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentCount(java.lang.Object)
+     */
+    @Override
+    public int getIncidentCount(E edge) 
+    {
+    	return 2;  // all tree edges have 2 incident vertices
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentEdges(java.lang.Object)
+     */
+    public Collection<E> getIncidentEdges(V vertex) 
+    {
+    	if (!containsVertex(vertex))
+    		return null;
+    	ArrayList<E> edges = new ArrayList<E>(order+1);
+    	VertexData v_data = vertex_data.get(vertex);
+    	if (v_data.parent_edge != null)
+    		edges.add(v_data.parent_edge);
+    	if (v_data.child_edges != null)
+    	{
+    		for (E edge : v_data.child_edges)
+    			if (edge != null)
+    				edges.add(edge);
+    	}
+    	if (edges.isEmpty())
+    		return Collections.emptySet();
+    	return Collections.unmodifiableCollection(edges);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentVertices(java.lang.Object)
+     */
+    @Override
+    public Collection<V> getIncidentVertices(E edge) 
+    {
+    	return edge_vpairs.get(edge);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#getNeighborCount(java.lang.Object)
+     */
+    @Override
+    public int getNeighborCount(V vertex) 
+    {
+        if (!containsVertex(vertex))
+            return 0;
+    	return (vertex.equals(root) ? 0 : 1) + this.getChildCount(vertex);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#getNeighbors(java.lang.Object)
+     */
+    public Collection<V> getNeighbors(V vertex) 
+    {
+    	if (!containsVertex(vertex))
+    		return null;
+    	ArrayList<V> vertices = new ArrayList<V>(order+1);
+    	VertexData v_data = vertex_data.get(vertex);
+    	if (v_data.parent_edge != null)
+    		vertices.add(edge_vpairs.get(v_data.parent_edge).getFirst());
+    	if (v_data.child_edges != null)
+    	{
+    		for (E edge : v_data.child_edges)
+    			if (edge != null)
+    				vertices.add(edge_vpairs.get(edge).getSecond());
+    	}
+    	if (vertices.isEmpty())
+    		return Collections.emptySet();
+    	return Collections.unmodifiableCollection(vertices);
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#getVertexCount()
+     */
+    public int getVertexCount() 
+    {
+    	return vertex_data.size();
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#getVertices()
+     */
+    public Collection<V> getVertices() 
+    {
+      return new ImmutableSet.Builder<V>().addAll(vertex_data.keySet()).build();
+      //CollectionUtils.unmodifiableCollection(vertex_data.keySet());
+    }
+  
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object)
+     */
+    public boolean removeEdge(E edge) 
+    {
+    	if (!containsEdge(edge))
+    		return false;
+    	
+    	removeVertex(edge_vpairs.get(edge).getSecond());
+    	edge_vpairs.remove(edge);
+    	
+    	return true;
+    }
+
+    /**
+     * @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object)
+     */
+    public boolean removeVertex(V vertex) 
+    {
+    	if (!containsVertex(vertex))
+    		return false;
+    	
+    	// recursively remove all of vertex's children
+		for(V v : getChildren(vertex))
+			removeVertex(v);
+
+		E parent_edge = getParentEdge(vertex);
+		edge_vpairs.remove(parent_edge);
+		List<E> edges = vertex_data.get(vertex).child_edges;
+		if (edges != null)
+			for (E edge : edges)
+				edge_vpairs.remove(edge);
+		vertex_data.remove(vertex);
+		
+		return true;
+    }
+	
+	protected class VertexData
+	{
+	    List<E> child_edges;
+		E parent_edge;
+		int depth;
+		
+		protected VertexData(E parent_edge, int depth)
+		{
+			this.parent_edge = parent_edge;
+			this.depth = depth;
+		}
+	}
+
+	@Override
+	public boolean addEdge(E edge, Pair<? extends V> endpoints, EdgeType edgeType) 
+	{
+	    if (edge == null || endpoints == null)
+	        throw new IllegalArgumentException("inputs must not be null");
+		return addEdge(edge, endpoints.getFirst(), endpoints.getSecond(), edgeType);
+	}
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/OrderedSparseMultigraph.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/OrderedSparseMultigraph.java
new file mode 100644
index 0000000..8c76222
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/OrderedSparseMultigraph.java
@@ -0,0 +1,129 @@
+/*
+ * Created on Oct 18, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * An implementation of <code>Graph</code> that orders its vertex and edge collections
+ * according to insertion time, is suitable for sparse graphs, and 
+ * permits directed, undirected, and parallel edges.
+ */
+ at SuppressWarnings("serial")
+public class OrderedSparseMultigraph<V,E> 
+    extends SparseMultigraph<V,E>
+    implements MultiGraph<V,E> {
+	
+    /**
+     * @param <V> the vertex type for the graph Supplier
+     * @param <E> the edge type for the graph Supplier
+     * @return a {@code Supplier} that creates an instance of this graph type.
+     */
+	public static <V,E> Supplier<Graph<V,E>> getFactory() { 
+		return new Supplier<Graph<V,E>> () {
+			public Graph<V,E> get() {
+				return new OrderedSparseMultigraph<V,E>();
+			}
+		};
+	}
+
+    /**
+     * Creates a new instance.
+     */
+    public OrderedSparseMultigraph()
+    {
+        vertices = new LinkedHashMap<V, Pair<Set<E>>>();
+        edges = new LinkedHashMap<E, Pair<V>>();
+        directedEdges = new LinkedHashSet<E>();
+    }
+
+    @Override
+    public boolean addVertex(V vertex) {
+        if(vertex == null) {
+            throw new IllegalArgumentException("vertex may not be null");
+        }
+        if (!containsVertex(vertex)) {
+            vertices.put(vertex, new Pair<Set<E>>(new LinkedHashSet<E>(), new LinkedHashSet<E>()));
+            return true;
+        } else {
+        	return false;
+        }
+    }
+
+
+    @Override
+    public Collection<V> getPredecessors(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        
+        Set<V> preds = new LinkedHashSet<V>();
+        for (E edge : getIncoming_internal(vertex)) {
+        	if(getEdgeType(edge) == EdgeType.DIRECTED) {
+        		preds.add(this.getSource(edge));
+        	} else {
+        		preds.add(getOpposite(vertex, edge));
+        	}
+        }
+        return Collections.unmodifiableCollection(preds);
+    }
+
+    @Override
+    public Collection<V> getSuccessors(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+
+        Set<V> succs = new LinkedHashSet<V>();
+        for (E edge : getOutgoing_internal(vertex)) {
+        	if(getEdgeType(edge) == EdgeType.DIRECTED) {
+        		succs.add(this.getDest(edge));
+        	} else {
+        		succs.add(getOpposite(vertex, edge));
+        	}
+        }
+        return Collections.unmodifiableCollection(succs);
+    }
+
+    @Override
+    public Collection<V> getNeighbors(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+
+        Collection<V> out = new LinkedHashSet<V>();
+        out.addAll(this.getPredecessors(vertex));
+        out.addAll(this.getSuccessors(vertex));
+        return out;
+    }
+
+    @Override
+    public Collection<E> getIncidentEdges(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        
+        Collection<E> out = new LinkedHashSet<E>();
+        out.addAll(this.getInEdges(vertex));
+        out.addAll(this.getOutEdges(vertex));
+        return out;
+    }
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SetHypergraph.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SetHypergraph.java
new file mode 100644
index 0000000..8803e3e
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SetHypergraph.java
@@ -0,0 +1,344 @@
+/*
+ * Created on Feb 4, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+/**
+ * An implementation of <code>Hypergraph</code> that is suitable for sparse graphs and 
+ * permits parallel edges.
+ */
+ at SuppressWarnings("serial")
+public class SetHypergraph<V,H> 
+	implements Hypergraph<V,H>, MultiGraph<V,H>, Serializable
+{
+    protected Map<V, Set<H>> vertices; // Map of vertices to incident hyperedge sets
+    protected Map<H, Set<V>> edges;    // Map of hyperedges to incident vertex sets
+ 
+    /**
+     * Returns a <code>Factory</code> which creates instances of this class.
+     * @param <V> vertex type of the hypergraph to be created
+     * @param <H> edge type of the hypergraph to be created
+     * @return a <code>Factory</code> which creates instances of this class
+     */
+    public static <V,H> Supplier<Hypergraph<V,H>> getFactory() {
+        return new Supplier<Hypergraph<V,H>> () {
+            public Hypergraph<V,H> get() {
+                return new SetHypergraph<V,H>();
+            }
+        };
+    }
+
+    /**
+     * Creates a <code>SetHypergraph</code> and initializes the internal data structures.
+     */
+    public SetHypergraph()
+    {
+        vertices = new HashMap<V, Set<H>>();
+        edges = new HashMap<H, Set<V>>();
+    }
+    
+    /**
+     * Adds <code>hyperedge</code> to this graph and connects them to the vertex collection <code>to_attach</code>.
+     * Any vertices in <code>to_attach</code> that appear more than once will only appear once in the
+     * incident vertex collection for <code>hyperedge</code>, that is, duplicates will be ignored.
+     * 
+     * @see Hypergraph#addEdge(Object, Collection)
+     */
+    public boolean addEdge(H hyperedge, Collection<? extends V> to_attach)
+    {
+        if (hyperedge == null)
+            throw new IllegalArgumentException("input hyperedge may not be null");
+        
+        if (to_attach == null)
+            throw new IllegalArgumentException("endpoints may not be null");
+
+        if(to_attach.contains(null)) 
+            throw new IllegalArgumentException("cannot add an edge with a null endpoint");
+        
+        Set<V> new_endpoints = new HashSet<V>(to_attach);
+        if (edges.containsKey(hyperedge))
+        {
+            Collection<V> attached = edges.get(hyperedge);
+            if (!attached.equals(new_endpoints))
+            {
+                throw new IllegalArgumentException("Edge " + hyperedge + 
+                        " exists in this graph with endpoints " + attached);
+            }
+            else
+                return false;
+        }
+        edges.put(hyperedge, new_endpoints);
+        for (V v : to_attach)
+        {
+            // add v if it's not already in the graph
+            addVertex(v);
+            
+            // associate v with hyperedge
+            vertices.get(v).add(hyperedge);
+        }
+        return true;
+    }
+    
+    /**
+     * @see Hypergraph#addEdge(Object, Collection, EdgeType)
+     */
+    public boolean addEdge(H hyperedge, Collection<? extends V> to_attach, 
+    	EdgeType edge_type)
+    {
+    	if (edge_type != EdgeType.UNDIRECTED)
+    		throw new IllegalArgumentException("Edge type for this " +
+    				"implementation must be EdgeType.HYPER, not " + 
+    				edge_type);
+    	return addEdge(hyperedge, to_attach);
+    }
+    
+    /**
+     * @see Hypergraph#getEdgeType(Object)
+     */
+    public EdgeType getEdgeType(H edge)
+    {
+        if (containsEdge(edge))
+            return EdgeType.UNDIRECTED;
+        else
+            return null;
+    }
+    
+    public boolean containsVertex(V vertex) {
+    	return vertices.keySet().contains(vertex);
+    }
+    
+    public boolean containsEdge(H edge) {
+    	return edges.keySet().contains(edge);
+    }
+
+    public Collection<H> getEdges()
+    {
+        return edges.keySet();
+    }
+    
+    public Collection<V> getVertices()
+    {
+        return vertices.keySet();
+    }
+
+    public int getEdgeCount()
+    {
+        return edges.size();
+    }
+    
+    public int getVertexCount()
+    {
+        return vertices.size();
+    }
+    
+    public Collection<V> getNeighbors(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        
+        Set<V> neighbors = new HashSet<V>();
+        for (H hyperedge : vertices.get(vertex))
+        {
+            neighbors.addAll(edges.get(hyperedge));
+        }
+        return neighbors;
+    }
+    
+    public Collection<H> getIncidentEdges(V vertex)
+    {
+        return vertices.get(vertex);
+    }
+    
+    public Collection<V> getIncidentVertices(H edge)
+    {
+        return edges.get(edge);
+    }
+    
+    public H findEdge(V v1, V v2)
+    {
+        if (!containsVertex(v1) || !containsVertex(v2))
+            return null;
+        
+        for (H h : getIncidentEdges(v1))
+        {
+            if (isIncident(v2, h))
+                return h;
+        }
+        return null;
+    }
+
+    public Collection<H> findEdgeSet(V v1, V v2)
+    {
+        if (!containsVertex(v1) || !containsVertex(v2))
+            return null;
+        
+        Collection<H> edges = new ArrayList<H>();
+        for (H h : getIncidentEdges(v1))
+        {
+            if (isIncident(v2, h))
+                edges.add(h);
+        }
+        return Collections.unmodifiableCollection(edges);
+    }
+    
+    public boolean addVertex(V vertex)
+    {
+    	if(vertex == null) 
+    	    throw new IllegalArgumentException("cannot add a null vertex");
+        if (containsVertex(vertex))
+            return false;
+        vertices.put(vertex, new HashSet<H>());
+        return true;
+    }
+    
+    public boolean removeVertex(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return false;
+        for (H hyperedge : vertices.get(vertex))
+        {
+            edges.get(hyperedge).remove(vertex);
+        }
+        vertices.remove(vertex);
+        return true;
+    }
+    
+    public boolean removeEdge(H hyperedge)
+    {
+        if (!containsEdge(hyperedge))
+            return false;
+        for (V vertex : edges.get(hyperedge))
+        {
+            vertices.get(vertex).remove(hyperedge);
+        }
+        edges.remove(hyperedge);
+        return true;
+    }
+    
+    public boolean isNeighbor(V v1, V v2)
+    {
+        if (!containsVertex(v1) || !containsVertex(v2))
+            return false;
+        
+        if (vertices.get(v2).isEmpty())
+            return false;
+        for (H hyperedge : vertices.get(v1))
+        {
+            if (edges.get(hyperedge).contains(v2))
+                return true;
+        }
+        return false;
+    }
+    
+    public boolean isIncident(V vertex, H edge)
+    {
+        if (!containsVertex(vertex) || !containsEdge(edge))
+            return false;
+        
+        return vertices.get(vertex).contains(edge);
+    }
+    
+    public int degree(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return 0;
+        
+        return vertices.get(vertex).size();
+    }
+    
+    public int getNeighborCount(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return 0;
+        
+        return getNeighbors(vertex).size();
+    }
+    
+    public int getIncidentCount(H edge)
+    {
+        if (!containsEdge(edge))
+            return 0;
+        
+        return edges.get(edge).size();
+    }
+
+    public int getEdgeCount(EdgeType edge_type)
+    {
+        if (edge_type == EdgeType.UNDIRECTED)
+            return edges.size();
+        return 0;
+    }
+
+    public Collection<H> getEdges(EdgeType edge_type)
+    {
+        if (edge_type == EdgeType.UNDIRECTED)
+            return edges.keySet();
+        return null;
+    }
+
+	public EdgeType getDefaultEdgeType() 
+	{
+		return EdgeType.UNDIRECTED;
+	}
+
+	public Collection<H> getInEdges(V vertex) 
+	{
+		return getIncidentEdges(vertex);
+	}
+
+	public Collection<H> getOutEdges(V vertex) 
+	{
+		return getIncidentEdges(vertex);
+	}
+
+	public int inDegree(V vertex) 
+	{
+		return degree(vertex);
+	}
+
+	public int outDegree(V vertex) 
+	{
+		return degree(vertex);
+	}
+
+	public V getDest(H directed_edge) 
+	{
+		return null;
+	}
+
+	public V getSource(H directed_edge) 
+	{
+		return null;
+	}
+
+	public Collection<V> getPredecessors(V vertex) 
+	{
+		return getNeighbors(vertex);
+	}
+
+	public Collection<V> getSuccessors(V vertex) 
+	{
+		return getNeighbors(vertex);
+	}
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SortedSparseMultigraph.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SortedSparseMultigraph.java
new file mode 100644
index 0000000..6ebff4e
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SortedSparseMultigraph.java
@@ -0,0 +1,128 @@
+/*
+ * Created on Oct 18, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.Ordering;
+
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * An implementation of <code>Graph</code> that is suitable for sparse graphs,
+ * orders its vertex and edge collections according to either specified <code>Comparator</code>
+ * instances or the natural ordering of their elements, and permits directed, undirected,
+ * and parallel edges. 
+ * 
+ * @author Joshua O'Madadhain
+ */
+ at SuppressWarnings("serial")
+public class SortedSparseMultigraph<V,E> 
+    extends OrderedSparseMultigraph<V,E>
+    implements MultiGraph<V,E> 
+{
+    /**
+     * @param <V> the vertex type for the graph Supplier
+     * @param <E> the edge type for the graph Supplier
+     * @return a {@code Supplier} that creates an instance of this graph type.
+     */
+	public static <V,E> Supplier<Graph<V,E>> getFactory() 
+	{ 
+		return new Supplier<Graph<V,E>> () 
+		{
+			public Graph<V,E> get() 
+			{
+				return new SortedSparseMultigraph<V,E>();
+			}
+		};
+	}
+    
+    /**
+     * <code>Comparator</code> used in ordering vertices.  Defaults to <code>util.ComparableComparator</code>
+     * if no comparators are specified in the constructor.
+     */
+    protected Comparator<V> vertex_comparator;
+
+    /**
+     * <code>Comparator</code> used in ordering edges.  Defaults to <code>util.ComparableComparator</code>
+     * if no comparators are specified in the constructor.
+     */
+    protected Comparator<E> edge_comparator;
+    
+    /**
+     * Creates a new instance which sorts its vertices and edges according to the 
+     * specified {@code Comparator}s.
+     * @param vertex_comparator specifies how the vertices are to be compared 
+     * @param edge_comparator specifies how the edges are to be compared
+     */
+    public SortedSparseMultigraph(Comparator<V> vertex_comparator, Comparator<E> edge_comparator)
+    {
+        this.vertex_comparator = vertex_comparator;
+        this.edge_comparator = edge_comparator;
+        vertices = new TreeMap<V, Pair<Set<E>>>(vertex_comparator);
+        edges = new TreeMap<E, Pair<V>>(edge_comparator);
+        directedEdges = new TreeSet<E>(edge_comparator);
+    }
+    
+    /**
+     * Creates a new instance which sorts its vertices and edges according to 
+     * their natural ordering.
+     */
+    public SortedSparseMultigraph()
+    {
+        this(new Ordering<V>(){
+			@SuppressWarnings("unchecked")
+			public int compare(V v1, V v2) {
+				return ((Comparable<V>) v1).compareTo(v2);
+			}},
+        		new Ordering<E>(){
+					@SuppressWarnings("unchecked")
+					public int compare(E e1, E e2) {
+						return ((Comparable<E>) e1).compareTo(e2);
+					}});
+    }
+
+    /**
+     * Provides a new {@code Comparator} to be used in sorting the vertices.
+     * @param vertex_comparator the comparator that defines the new ordering
+     */
+    public void setVertexComparator(Comparator<V> vertex_comparator)
+    {
+        this.vertex_comparator = vertex_comparator;
+        Map<V, Pair<Set<E>>> tmp_vertices = new TreeMap<V, Pair<Set<E>>>(vertex_comparator);
+        for (Map.Entry<V, Pair<Set<E>>> entry : vertices.entrySet())
+            tmp_vertices.put(entry.getKey(), entry.getValue());
+        this.vertices = tmp_vertices;
+    }
+    
+    @Override
+    public boolean addVertex(V vertex) {
+        if(vertex == null) {
+            throw new IllegalArgumentException("vertex may not be null");
+        }
+        if (!containsVertex(vertex)) 
+        {
+            vertices.put(vertex, new Pair<Set<E>>(new TreeSet<E>(edge_comparator), 
+                new TreeSet<E>(edge_comparator)));
+            return true;
+        } 
+        else 
+        {
+        	return false;
+        }
+    }
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SparseGraph.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SparseGraph.java
new file mode 100644
index 0000000..517f045
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SparseGraph.java
@@ -0,0 +1,373 @@
+/*
+ * Created on Apr 15, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * An implementation of <code>Graph</code> that is suitable for sparse graphs and
+ * permits both directed and undirected edges.
+ */
+ at SuppressWarnings("serial")
+public class SparseGraph<V,E> 
+    extends AbstractGraph<V,E> 
+    implements Graph<V,E>
+{
+    /**
+     * @param <V> the vertex type for the graph Supplier
+     * @param <E> the edge type for the graph Supplier
+     * @return a {@code Supplier} that creates an instance of this graph type.
+     */
+    public static <V,E> Supplier<Graph<V,E>> getFactory() 
+    { 
+        return new Supplier<Graph<V,E>> () 
+        {
+            public Graph<V,E> get() 
+            {
+                return new SparseGraph<V,E>();
+            }
+        };
+    }
+
+    protected static final int INCOMING = 0;
+    protected static final int OUTGOING = 1;
+    protected static final int INCIDENT = 2;
+    
+    protected Map<V, Map<V,E>[]> vertex_maps; // Map of vertices to adjacency maps of vertices to {incoming, outgoing, incident} edges
+    protected Map<E, Pair<V>> directed_edges;    // Map of directed edges to incident vertex sets
+    protected Map<E, Pair<V>> undirected_edges;    // Map of undirected edges to incident vertex sets
+    
+    /**
+     * Creates an instance.
+     */
+    public SparseGraph()
+    {
+        vertex_maps = new HashMap<V, Map<V,E>[]>();
+        directed_edges = new HashMap<E, Pair<V>>();
+        undirected_edges = new HashMap<E, Pair<V>>();
+    }
+    
+    @Override
+    public E findEdge(V v1, V v2)
+    {
+        if (!containsVertex(v1) || !containsVertex(v2))
+            return null;
+        E edge = vertex_maps.get(v1)[OUTGOING].get(v2);
+        if (edge == null)
+            edge = vertex_maps.get(v1)[INCIDENT].get(v2);
+        return edge;
+    }
+
+    @Override
+    public Collection<E> findEdgeSet(V v1, V v2)
+    {
+        if (!containsVertex(v1) || !containsVertex(v2))
+            return null;
+        Collection<E> edges = new ArrayList<E>(2);
+        E e1 = vertex_maps.get(v1)[OUTGOING].get(v2);
+        if (e1 != null)
+            edges.add(e1);
+        E e2 = vertex_maps.get(v1)[INCIDENT].get(v2);
+        if (e1 != null)
+            edges.add(e2);
+        return edges;
+    }
+    
+    @Override
+    public boolean addEdge(E edge, Pair<? extends V> endpoints, EdgeType edgeType)
+    {
+        Pair<V> new_endpoints = getValidatedEndpoints(edge, endpoints);
+        if (new_endpoints == null)
+            return false;
+        
+        V v1 = new_endpoints.getFirst();
+        V v2 = new_endpoints.getSecond();
+        
+        // undirected edges and directed edges are not considered to be parallel to each other,
+        // so as long as anything that's returned by findEdge is not of the same type as
+        // edge, we're fine
+        E connection = findEdge(v1, v2);
+        if (connection != null && getEdgeType(connection) == edgeType)
+            return false;
+
+        if (!containsVertex(v1))
+            this.addVertex(v1);
+        
+        if (!containsVertex(v2))
+            this.addVertex(v2);
+        
+        // map v1 to <v2, edge> and vice versa
+        if (edgeType == EdgeType.DIRECTED)
+        {
+            vertex_maps.get(v1)[OUTGOING].put(v2, edge);
+            vertex_maps.get(v2)[INCOMING].put(v1, edge);
+            directed_edges.put(edge, new_endpoints);
+        }
+        else
+        {
+            vertex_maps.get(v1)[INCIDENT].put(v2, edge);
+            vertex_maps.get(v2)[INCIDENT].put(v1, edge);
+            undirected_edges.put(edge, new_endpoints);
+        }
+        
+        return true;
+    }
+
+    
+    
+    public Collection<E> getInEdges(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        
+        // combine directed inedges and undirected
+        Collection<E> in = new HashSet<E>(vertex_maps.get(vertex)[INCOMING].values());
+        in.addAll(vertex_maps.get(vertex)[INCIDENT].values());
+        return Collections.unmodifiableCollection(in);
+    }
+
+    public Collection<E> getOutEdges(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        
+        // combine directed outedges and undirected
+        Collection<E> out = new HashSet<E>(vertex_maps.get(vertex)[OUTGOING].values());
+        out.addAll(vertex_maps.get(vertex)[INCIDENT].values());
+        return Collections.unmodifiableCollection(out);
+    }
+
+    public Collection<V> getPredecessors(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        
+        // consider directed inedges and undirected
+        Collection<V> preds = new HashSet<V>(vertex_maps.get(vertex)[INCOMING].keySet());
+        preds.addAll(vertex_maps.get(vertex)[INCIDENT].keySet());
+        return Collections.unmodifiableCollection(preds);
+    }
+
+    public Collection<V> getSuccessors(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        
+        // consider directed outedges and undirected
+        Collection<V> succs = new HashSet<V>(vertex_maps.get(vertex)[OUTGOING].keySet());
+        succs.addAll(vertex_maps.get(vertex)[INCIDENT].keySet());
+        return Collections.unmodifiableCollection(succs);
+    }
+
+    public Collection<E> getEdges(EdgeType edgeType)
+    {
+        if (edgeType == EdgeType.DIRECTED)
+            return Collections.unmodifiableCollection(directed_edges.keySet());
+        else if (edgeType == EdgeType.UNDIRECTED)
+            return Collections.unmodifiableCollection(undirected_edges.keySet());
+        else
+            return null;
+    }
+
+    public Pair<V> getEndpoints(E edge)
+    {
+        Pair<V> endpoints;
+        endpoints = directed_edges.get(edge);
+        if (endpoints == null)
+            return undirected_edges.get(edge);
+        else
+            return endpoints;
+    }
+
+    public EdgeType getEdgeType(E edge)
+    {
+        if (directed_edges.containsKey(edge))
+            return EdgeType.DIRECTED;
+        else if (undirected_edges.containsKey(edge))
+            return EdgeType.UNDIRECTED;
+        else
+            return null;
+    }
+
+    public V getSource(E directed_edge)
+    {
+        if (getEdgeType(directed_edge) == EdgeType.DIRECTED)
+            return directed_edges.get(directed_edge).getFirst();
+        else
+            return null;
+    }
+
+    public V getDest(E directed_edge)
+    {
+        if (getEdgeType(directed_edge) == EdgeType.DIRECTED)
+            return directed_edges.get(directed_edge).getSecond();
+        else
+            return null;
+    }
+
+    public boolean isSource(V vertex, E edge)
+    {
+        if (!containsVertex(vertex) || !containsEdge(edge))
+            return false;
+        
+        V source = getSource(edge);
+        if (source != null)
+            return source.equals(vertex);
+        else
+            return false;
+    }
+
+    public boolean isDest(V vertex, E edge)
+    {
+        if (!containsVertex(vertex) || !containsEdge(edge))
+            return false;
+        
+        V dest = getDest(edge);
+        if (dest != null)
+            return dest.equals(vertex);
+        else
+            return false;
+    }
+
+    public Collection<E> getEdges()
+    {
+        Collection<E> edges = new ArrayList<E>(directed_edges.keySet());
+        edges.addAll(undirected_edges.keySet());
+        return Collections.unmodifiableCollection(edges);
+    }
+
+    public Collection<V> getVertices()
+    {
+        return Collections.unmodifiableCollection(vertex_maps.keySet());
+    }
+
+    public boolean containsVertex(V vertex)
+    {
+        return vertex_maps.containsKey(vertex);
+    }
+
+    public boolean containsEdge(E edge)
+    {
+        return directed_edges.containsKey(edge) || undirected_edges.containsKey(edge);
+    }
+
+    public int getEdgeCount()
+    {
+        return directed_edges.size() + undirected_edges.size();
+    }
+
+    public int getVertexCount()
+    {
+        return vertex_maps.size();
+    }
+
+    public Collection<V> getNeighbors(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        // consider directed edges and undirected edges
+        Collection<V> neighbors = new HashSet<V>(vertex_maps.get(vertex)[INCOMING].keySet());
+        neighbors.addAll(vertex_maps.get(vertex)[OUTGOING].keySet());
+        neighbors.addAll(vertex_maps.get(vertex)[INCIDENT].keySet());
+        return Collections.unmodifiableCollection(neighbors);
+    }
+
+    public Collection<E> getIncidentEdges(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        Collection<E> incident = new HashSet<E>(vertex_maps.get(vertex)[INCOMING].values());
+        incident.addAll(vertex_maps.get(vertex)[OUTGOING].values());
+        incident.addAll(vertex_maps.get(vertex)[INCIDENT].values());
+        return Collections.unmodifiableCollection(incident);
+    }
+
+    @SuppressWarnings("unchecked")
+    public boolean addVertex(V vertex)
+    {
+        if(vertex == null) {
+            throw new IllegalArgumentException("vertex may not be null");
+        }
+        if (!containsVertex(vertex)) {
+            vertex_maps.put(vertex, new HashMap[]{new HashMap<V,E>(), new HashMap<V,E>(), new HashMap<V,E>()});
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public boolean removeVertex(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return false;
+        
+        // copy to avoid concurrent modification in removeEdge
+        Collection<E> incident = new ArrayList<E>(getIncidentEdges(vertex));
+        
+        for (E edge : incident)
+            removeEdge(edge);
+        
+        vertex_maps.remove(vertex);
+        
+        return true;
+    }
+
+    public boolean removeEdge(E edge)
+    {
+        if (!containsEdge(edge)) 
+            return false;
+        
+        Pair<V> endpoints = getEndpoints(edge);
+        V v1 = endpoints.getFirst();
+        V v2 = endpoints.getSecond();
+        
+        // remove edge from incident vertices' adjacency maps
+        if (getEdgeType(edge) == EdgeType.DIRECTED)
+        {
+            vertex_maps.get(v1)[OUTGOING].remove(v2);
+            vertex_maps.get(v2)[INCOMING].remove(v1);
+            directed_edges.remove(edge);
+        }
+        else
+        {
+            vertex_maps.get(v1)[INCIDENT].remove(v2);
+            vertex_maps.get(v2)[INCIDENT].remove(v1);
+            undirected_edges.remove(edge);
+        }
+
+        return true;
+    }
+    
+    public int getEdgeCount(EdgeType edge_type)
+    {
+        if (edge_type == EdgeType.DIRECTED)
+            return directed_edges.size();
+        if (edge_type == EdgeType.UNDIRECTED)
+            return undirected_edges.size();
+        return 0;
+    }
+
+	public EdgeType getDefaultEdgeType() 
+	{
+		return EdgeType.UNDIRECTED;
+	}
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SparseMultigraph.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SparseMultigraph.java
new file mode 100644
index 0000000..5b6ab17
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/SparseMultigraph.java
@@ -0,0 +1,320 @@
+/*
+ * Created on Oct 18, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * An implementation of <code>Graph</code> that is suitable for sparse graphs
+ * and permits directed, undirected, and parallel edges.
+ */
+ at SuppressWarnings("serial")
+public class SparseMultigraph<V,E> 
+    extends AbstractGraph<V,E>
+    implements MultiGraph<V,E> {
+	
+    /**
+     * @param <V> the vertex type for the graph Supplier
+     * @param <E> the edge type for the graph Supplier
+     * @return a {@code Supplier} that creates an instance of this graph type
+     */
+	public static <V,E> Supplier<Graph<V,E>> getFactory() { 
+		return new Supplier<Graph<V,E>> () {
+			public Graph<V,E> get() {
+				return new SparseMultigraph<V,E>();
+			}
+		};
+	}
+    
+    // TODO: refactor internal representation: right now directed edges each have two references (in vertices and directedEdges)
+    // and undirected also have two (incoming and outgoing).  
+    protected Map<V, Pair<Set<E>>> vertices; // Map of vertices to Pair of adjacency sets {incoming, outgoing}
+    protected Map<E, Pair<V>> edges;            // Map of edges to incident vertex pairs
+    protected Set<E> directedEdges;
+
+    /**
+     * Creates a new instance.
+     */
+    public SparseMultigraph()
+    {
+        vertices = new HashMap<V, Pair<Set<E>>>();
+        edges = new HashMap<E, Pair<V>>();
+        directedEdges = new HashSet<E>();
+    }
+
+    public Collection<E> getEdges()
+    {
+        return Collections.unmodifiableCollection(edges.keySet());
+    }
+
+    public Collection<V> getVertices()
+    {
+        return Collections.unmodifiableCollection(vertices.keySet());
+    }
+    
+    public boolean containsVertex(V vertex) {
+    	return vertices.keySet().contains(vertex);
+    }
+    
+    public boolean containsEdge(E edge) {
+    	return edges.keySet().contains(edge);
+    }
+
+    protected Collection<E> getIncoming_internal(V vertex)
+    {
+        return vertices.get(vertex).getFirst();
+    }
+    
+    protected Collection<E> getOutgoing_internal(V vertex)
+    {
+        return vertices.get(vertex).getSecond();
+    }
+    
+    public boolean addVertex(V vertex) {
+        if(vertex == null) {
+            throw new IllegalArgumentException("vertex may not be null");
+        }
+        if (!vertices.containsKey(vertex)) {
+            vertices.put(vertex, new Pair<Set<E>>(new HashSet<E>(), new HashSet<E>()));
+            return true;
+        } else {
+        	return false;
+        }
+    }
+
+    public boolean removeVertex(V vertex) {
+        if (!containsVertex(vertex))
+            return false;
+        
+        // copy to avoid concurrent modification in removeEdge
+        Set<E> incident = new HashSet<E>(getIncoming_internal(vertex));
+        incident.addAll(getOutgoing_internal(vertex));
+        
+        for (E edge : incident)
+            removeEdge(edge);
+        
+        vertices.remove(vertex);
+        
+        return true;
+    }
+    
+    @Override
+    public boolean addEdge(E edge, Pair<? extends V> endpoints, EdgeType edgeType) {
+
+        Pair<V> new_endpoints = getValidatedEndpoints(edge, endpoints);
+        if (new_endpoints == null)
+            return false;
+        
+        V v1 = new_endpoints.getFirst();
+        V v2 = new_endpoints.getSecond();
+        
+        if (!vertices.containsKey(v1))
+            this.addVertex(v1);
+        
+        if (!vertices.containsKey(v2))
+            this.addVertex(v2);
+        
+
+        vertices.get(v1).getSecond().add(edge);        
+        vertices.get(v2).getFirst().add(edge);        
+        edges.put(edge, new_endpoints);
+        if(edgeType == EdgeType.DIRECTED) {
+        	directedEdges.add(edge);
+        } else {
+          vertices.get(v1).getFirst().add(edge);        
+          vertices.get(v2).getSecond().add(edge);        
+        }
+        return true;
+    }
+    
+    public boolean removeEdge(E edge)
+    {
+        if (!containsEdge(edge)) {
+            return false;
+        }
+        
+        Pair<V> endpoints = getEndpoints(edge);
+        V v1 = endpoints.getFirst();
+        V v2 = endpoints.getSecond();
+        
+        // remove edge from incident vertices' adjacency sets
+        vertices.get(v1).getSecond().remove(edge);
+        vertices.get(v2).getFirst().remove(edge);
+
+        if(directedEdges.remove(edge) == false) {
+        	
+        	// its an undirected edge, remove the other ends
+            vertices.get(v2).getSecond().remove(edge);
+            vertices.get(v1).getFirst().remove(edge);
+        }
+        edges.remove(edge);
+        return true;
+    }
+    
+    public Collection<E> getInEdges(V vertex)
+    {
+    	if (!containsVertex(vertex))
+    		return null;
+        return Collections.unmodifiableCollection(vertices.get(vertex).getFirst());
+    }
+
+    public Collection<E> getOutEdges(V vertex)
+    {
+    	if (!containsVertex(vertex))
+    		return null;
+        return Collections.unmodifiableCollection(vertices.get(vertex).getSecond());
+    }
+
+    // TODO: this will need to get changed if we modify the internal representation
+    public Collection<V> getPredecessors(V vertex)
+    {
+    	if (!containsVertex(vertex))
+    		return null;
+
+        Set<V> preds = new HashSet<V>();
+        for (E edge : getIncoming_internal(vertex)) {
+        	if(getEdgeType(edge) == EdgeType.DIRECTED) {
+        		preds.add(this.getSource(edge));
+        	} else {
+        		preds.add(getOpposite(vertex, edge));
+        	}
+        }
+        return Collections.unmodifiableCollection(preds);
+    }
+
+    // TODO: this will need to get changed if we modify the internal representation
+    public Collection<V> getSuccessors(V vertex)
+    {
+    	if (!containsVertex(vertex))
+    		return null;
+        Set<V> succs = new HashSet<V>();
+        for (E edge : getOutgoing_internal(vertex)) {
+        	if(getEdgeType(edge) == EdgeType.DIRECTED) {
+        		succs.add(this.getDest(edge));
+        	} else {
+        		succs.add(getOpposite(vertex, edge));
+        	}
+        }
+        return Collections.unmodifiableCollection(succs);
+    }
+
+    public Collection<V> getNeighbors(V vertex)
+    {
+    	if (!containsVertex(vertex))
+    		return null;
+        Collection<V> out = new HashSet<V>();
+        out.addAll(this.getPredecessors(vertex));
+        out.addAll(this.getSuccessors(vertex));
+        return out;
+    }
+
+    public Collection<E> getIncidentEdges(V vertex)
+    {
+    	if (!containsVertex(vertex))
+    		return null;
+        Collection<E> out = new HashSet<E>();
+        out.addAll(this.getInEdges(vertex));
+        out.addAll(this.getOutEdges(vertex));
+        return out;
+    }
+
+    @Override
+    public E findEdge(V v1, V v2)
+    {
+    	if (!containsVertex(v1) || !containsVertex(v2))
+    		return null;
+        for (E edge : getOutgoing_internal(v1))
+            if (this.getOpposite(v1, edge).equals(v2))
+                return edge;
+        
+        return null;
+    }
+
+    public Pair<V> getEndpoints(E edge)
+    {
+        return edges.get(edge);
+    }
+
+    public V getSource(E edge) {
+    	if(directedEdges.contains(edge)) {
+    		return this.getEndpoints(edge).getFirst();
+    	}
+    	return null;
+    }
+
+    public V getDest(E edge) {
+    	if(directedEdges.contains(edge)) {
+    		return this.getEndpoints(edge).getSecond();
+    	}
+    	return null;
+    }
+
+    public boolean isSource(V vertex, E edge) {
+    	if (!containsEdge(edge) || !containsVertex(vertex))
+    		return false;
+        return getSource(edge).equals(vertex);
+    }
+
+    public boolean isDest(V vertex, E edge) {
+    	if (!containsEdge(edge) || !containsVertex(vertex))
+    		return false;
+        return getDest(edge).equals(vertex);
+    }
+
+    public EdgeType getEdgeType(E edge) {
+    	return directedEdges.contains(edge) ?
+    		EdgeType.DIRECTED :
+    			EdgeType.UNDIRECTED;
+    }
+
+	@SuppressWarnings("unchecked")
+	public Collection<E> getEdges(EdgeType edgeType) {
+		if(edgeType == EdgeType.DIRECTED) {
+			return Collections.unmodifiableSet(this.directedEdges);
+		} else if(edgeType == EdgeType.UNDIRECTED) {
+			Collection<E> edges = new HashSet<E>(getEdges());
+			edges.removeAll(directedEdges);
+			return edges;
+		} else {
+			return Collections.EMPTY_SET;
+		}
+		
+	}
+
+	public int getEdgeCount() {
+		return edges.keySet().size();
+	}
+
+	public int getVertexCount() {
+		return vertices.keySet().size();
+	}
+
+    public int getEdgeCount(EdgeType edge_type)
+    {
+        return getEdges(edge_type).size();
+    }
+
+	public EdgeType getDefaultEdgeType() 
+	{
+		return EdgeType.UNDIRECTED;
+	}
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/UndirectedOrderedSparseMultigraph.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/UndirectedOrderedSparseMultigraph.java
new file mode 100644
index 0000000..ead73a4
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/UndirectedOrderedSparseMultigraph.java
@@ -0,0 +1,88 @@
+/*
+ * Created on Oct 18, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * An implementation of <code>UndirectedGraph</code> that is suitable for sparse graphs,
+ * orders its vertex and edge collections according to insertion time, and permits
+ * parallel edges.
+ */
+ at SuppressWarnings("serial")
+public class UndirectedOrderedSparseMultigraph<V,E> 
+    extends UndirectedSparseMultigraph<V,E>
+    implements UndirectedGraph<V,E> {
+	
+    /**
+     * @param <V> the vertex type for the graph Supplier
+     * @param <E> the edge type for the graph Supplier
+     * @return a {@code Supplier} that creates an instance of this graph type.
+     */
+	public static <V,E> Supplier<UndirectedGraph<V,E>> getFactory() {
+		return new Supplier<UndirectedGraph<V,E>> () {
+			public UndirectedGraph<V,E> get() {
+				return new UndirectedOrderedSparseMultigraph<V,E>();
+			}
+		};
+	}
+
+	/**
+	 * Creates a new instance.
+	 */
+    public UndirectedOrderedSparseMultigraph() {
+        vertices = new LinkedHashMap<V, Set<E>>();
+        edges = new LinkedHashMap<E, Pair<V>>();
+    }
+
+    @Override
+    public boolean addVertex(V vertex) {
+    	if(vertex == null) {
+    		throw new IllegalArgumentException("vertex may not be null");
+    	}
+        if (!containsVertex(vertex))
+        {
+            vertices.put(vertex, new LinkedHashSet<E>());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public Collection<V> getNeighbors(V vertex) {
+        if (!containsVertex(vertex))
+            return null;
+        
+        Set<V> neighbors = new LinkedHashSet<V>();
+        for (E edge : getIncident_internal(vertex))
+        {
+            Pair<V> endpoints = this.getEndpoints(edge);
+            V e_a = endpoints.getFirst();
+            V e_b = endpoints.getSecond();
+            if (vertex.equals(e_a))
+                neighbors.add(e_b);
+            else
+                neighbors.add(e_a);
+        }
+        
+        return Collections.unmodifiableCollection(neighbors);
+    }
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/UndirectedSparseGraph.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/UndirectedSparseGraph.java
new file mode 100644
index 0000000..c8d66ae
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/UndirectedSparseGraph.java
@@ -0,0 +1,243 @@
+/*
+ * Created on Apr 1, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * An implementation of <code>UndirectedGraph</code> that is suitable
+ * for sparse graphs.
+ */
+ at SuppressWarnings("serial")
+public class UndirectedSparseGraph<V, E> extends AbstractTypedGraph<V, E>
+        implements UndirectedGraph<V, E>
+{
+
+    /**
+     * @param <V> the vertex type for the graph Supplier
+     * @param <E> the edge type for the graph Supplier
+     * @return a {@code Supplier} that creates an instance of this graph type.
+     */
+    public static <V,E> Supplier<UndirectedGraph<V,E>> getFactory() {
+        return new Supplier<UndirectedGraph<V,E>> () {
+
+            public UndirectedGraph<V,E> get() {
+                return new UndirectedSparseGraph<V,E>();
+            }
+        };
+    }
+
+    protected Map<V, Map<V,E>> vertices; // Map of vertices to adjacency maps of vertices to incident edges
+    protected Map<E, Pair<V>> edges;    // Map of edges to incident vertex sets
+
+    /**
+     * Creates an instance.
+     */
+    public UndirectedSparseGraph() {
+    	super(EdgeType.UNDIRECTED);
+        vertices = new HashMap<V, Map<V,E>>();
+        edges = new HashMap<E, Pair<V>>();
+    }
+
+    @Override
+    public boolean addEdge(E edge, Pair<? extends V> endpoints, EdgeType edgeType)
+    {
+    	this.validateEdgeType(edgeType);
+        Pair<V> new_endpoints = getValidatedEndpoints(edge, endpoints);
+        if (new_endpoints == null)
+            return false;
+        
+        V v1 = new_endpoints.getFirst();
+        V v2 = new_endpoints.getSecond();
+        
+        if (findEdge(v1, v2) != null)
+            return false;
+        
+        edges.put(edge, new_endpoints);
+
+        if (!vertices.containsKey(v1))
+            this.addVertex(v1);
+        
+        if (!vertices.containsKey(v2))
+            this.addVertex(v2);
+        
+        // map v1 to <v2, edge> and vice versa
+        vertices.get(v1).put(v2, edge);
+        vertices.get(v2).put(v1, edge);
+
+        return true;
+    }
+
+    public Collection<E> getInEdges(V vertex)
+    {
+        return this.getIncidentEdges(vertex);
+    }
+
+    public Collection<E> getOutEdges(V vertex)
+    {
+        return this.getIncidentEdges(vertex);
+    }
+
+    public Collection<V> getPredecessors(V vertex)
+    {
+        return this.getNeighbors(vertex);
+    }
+
+    public Collection<V> getSuccessors(V vertex)
+    {
+        return this.getNeighbors(vertex);
+    }
+
+    @Override
+    public E findEdge(V v1, V v2)
+    {
+        if (!containsVertex(v1) || !containsVertex(v2))
+            return null;
+        return vertices.get(v1).get(v2);
+    }
+    
+    @Override
+    public Collection<E> findEdgeSet(V v1, V v2)
+    {
+        if (!containsVertex(v1) || !containsVertex(v2))
+            return null;
+        ArrayList<E> edge_collection = new ArrayList<E>(1);
+//        if (!containsVertex(v1) || !containsVertex(v2))
+//            return edge_collection;
+        E e = findEdge(v1, v2);
+        if (e == null)
+            return edge_collection;
+        edge_collection.add(e);
+        return edge_collection;
+    }
+    
+    public Pair<V> getEndpoints(E edge)
+    {
+        return edges.get(edge);
+    }
+
+    public V getSource(E directed_edge)
+    {
+        return null;
+    }
+
+    public V getDest(E directed_edge)
+    {
+        return null;
+    }
+
+    public boolean isSource(V vertex, E edge)
+    {
+        return false;
+    }
+
+    public boolean isDest(V vertex, E edge)
+    {
+        return false;
+    }
+
+    public Collection<E> getEdges()
+    {
+        return Collections.unmodifiableCollection(edges.keySet());
+    }
+
+    public Collection<V> getVertices()
+    {
+        return Collections.unmodifiableCollection(vertices.keySet());
+    }
+
+    public boolean containsVertex(V vertex)
+    {
+        return vertices.containsKey(vertex);
+    }
+
+    public boolean containsEdge(E edge)
+    {
+        return edges.containsKey(edge);
+    }
+
+    public int getEdgeCount()
+    {
+        return edges.size();
+    }
+
+    public int getVertexCount()
+    {
+        return vertices.size();
+    }
+
+    public Collection<V> getNeighbors(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        return Collections.unmodifiableCollection(vertices.get(vertex).keySet());
+    }
+
+    public Collection<E> getIncidentEdges(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return null;
+        return Collections.unmodifiableCollection(vertices.get(vertex).values());
+    }
+
+    public boolean addVertex(V vertex)
+    {
+        if(vertex == null) {
+            throw new IllegalArgumentException("vertex may not be null");
+        }
+        if (!containsVertex(vertex)) {
+            vertices.put(vertex, new HashMap<V,E>());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public boolean removeVertex(V vertex)
+    {
+        if (!containsVertex(vertex))
+            return false;
+
+        // iterate over copy of incident edge collection
+        for (E edge : new ArrayList<E>(vertices.get(vertex).values()))
+            removeEdge(edge);
+        
+        vertices.remove(vertex);
+        return true;
+    }
+
+    public boolean removeEdge(E edge)
+    {
+        if (!containsEdge(edge))
+            return false;
+        
+        Pair<V> endpoints = getEndpoints(edge);
+        V v1 = endpoints.getFirst();
+        V v2 = endpoints.getSecond();
+        
+        // remove incident vertices from each others' adjacency maps
+        vertices.get(v1).remove(v2);
+        vertices.get(v2).remove(v1);
+
+        edges.remove(edge);
+        return true;
+    }
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/UndirectedSparseMultigraph.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/UndirectedSparseMultigraph.java
new file mode 100644
index 0000000..3c14de5
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/UndirectedSparseMultigraph.java
@@ -0,0 +1,249 @@
+/*
+ * Created on Mar 6, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Oct 18, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * An implementation of <code>UndirectedGraph</code> that is suitable for 
+ * sparse graphs and permits parallel edges.
+ */
+ at SuppressWarnings("serial")
+public class UndirectedSparseMultigraph<V,E> 
+    extends AbstractTypedGraph<V,E>
+    implements UndirectedGraph<V,E>, MultiGraph<V,E>
+{
+    /**
+     * @param <V> the vertex type for the graph Supplier
+     * @param <E> the edge type for the graph Supplier
+     * @return a {@code Supplier} that creates an instance of this graph type.
+     */
+    public static <V,E> Supplier<UndirectedGraph<V,E>> getFactory() {
+        return new Supplier<UndirectedGraph<V,E>> () {
+
+            public UndirectedGraph<V,E> get() {
+                return new UndirectedSparseMultigraph<V,E>();
+            }
+        };
+    }
+
+    protected Map<V, Set<E>> vertices; // Map of vertices to adjacency sets
+    protected Map<E, Pair<V>> edges;    // Map of edges to incident vertex sets
+
+    /**
+     * Creates a new instance.
+     */
+    public UndirectedSparseMultigraph() {
+    	super(EdgeType.UNDIRECTED);
+        vertices = new HashMap<V, Set<E>>();
+        edges = new HashMap<E, Pair<V>>();
+    }
+
+    public Collection<E> getEdges() {
+        return Collections.unmodifiableCollection(edges.keySet());
+    }
+
+    public Collection<V> getVertices() {
+        return Collections.unmodifiableCollection(vertices.keySet());
+    }
+
+    public boolean containsVertex(V vertex) {
+    	return vertices.keySet().contains(vertex);
+    }
+    
+    public boolean containsEdge(E edge) {
+    	return edges.keySet().contains(edge);
+    }
+
+    protected Collection<E> getIncident_internal(V vertex)
+    {
+        return vertices.get(vertex);
+    }
+    
+    public boolean addVertex(V vertex) {
+        if(vertex == null) {
+            throw new IllegalArgumentException("vertex may not be null");
+        }
+        if (!containsVertex(vertex))
+        {
+            vertices.put(vertex, new HashSet<E>());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public boolean removeVertex(V vertex) {
+        if (!containsVertex(vertex))
+            return false;
+        
+        for (E edge : new ArrayList<E>(getIncident_internal(vertex)))
+            removeEdge(edge);
+        
+        vertices.remove(vertex);
+        return true;
+    }
+    
+    @Override
+    public boolean addEdge(E edge, V v1, V v2, EdgeType edgeType) {
+        return addEdge(edge, new Pair<V>(v1, v2), edgeType);
+    }
+    
+    @Override
+    public boolean addEdge(E edge, Pair<? extends V> endpoints, EdgeType edge_type) 
+    {
+    	validateEdgeType(edge_type);
+    	
+        Pair<V> new_endpoints = getValidatedEndpoints(edge, endpoints);
+        if (new_endpoints == null)
+            return false;
+        
+        V v1 = endpoints.getFirst();
+        V v2 = endpoints.getSecond();
+
+        edges.put(edge, new_endpoints);
+        
+        if (!containsVertex(v1))
+            this.addVertex(v1);
+        
+        if (!containsVertex(v2))
+            this.addVertex(v2);
+
+        vertices.get(v1).add(edge);
+        vertices.get(v2).add(edge);        
+        
+        return true;
+    }
+
+    public boolean removeEdge(E edge) {
+        if (!containsEdge(edge))
+            return false;
+        
+        Pair<V> endpoints = getEndpoints(edge);
+        V v1 = endpoints.getFirst();
+        V v2 = endpoints.getSecond();
+        
+        // remove edge from incident vertices' adjacency sets
+        vertices.get(v1).remove(edge);
+        vertices.get(v2).remove(edge);
+
+        edges.remove(edge);
+        return true;
+    }
+    
+    public Collection<E> getInEdges(V vertex) {
+        return this.getIncidentEdges(vertex);
+    }
+
+    public Collection<E> getOutEdges(V vertex) {
+        return this.getIncidentEdges(vertex);
+    }
+
+    public Collection<V> getPredecessors(V vertex) {
+        return this.getNeighbors(vertex);
+    }
+
+    public Collection<V> getSuccessors(V vertex) {
+        return this.getNeighbors(vertex);
+    }
+
+    public Collection<V> getNeighbors(V vertex) {
+        if (!containsVertex(vertex))
+            return null;
+        
+        Set<V> neighbors = new HashSet<V>();
+        for (E edge : getIncident_internal(vertex))
+        {
+            Pair<V> endpoints = this.getEndpoints(edge);
+            V e_a = endpoints.getFirst();
+            V e_b = endpoints.getSecond();
+            if (vertex.equals(e_a))
+                neighbors.add(e_b);
+            else
+                neighbors.add(e_a);
+        }
+        
+        return Collections.unmodifiableCollection(neighbors);
+    }
+
+    public Collection<E> getIncidentEdges(V vertex) {
+        if (!containsVertex(vertex))
+            return null;
+        
+        return Collections.unmodifiableCollection(getIncident_internal(vertex));
+    }
+
+    @Override
+    public E findEdge(V v1, V v2) {
+        if (!containsVertex(v1) || !containsVertex(v2))
+            return null;
+        for (E edge : getIncident_internal(v1)) {
+            Pair<V> endpoints = this.getEndpoints(edge);
+            V e_a = endpoints.getFirst();
+            V e_b = endpoints.getSecond();
+            if ((v1.equals(e_a) && v2.equals(e_b)) || (v1.equals(e_b) && v2.equals(e_a)))
+                return edge;
+        }
+        return null;
+    }
+
+    public Pair<V> getEndpoints(E edge) {
+        return edges.get(edge);
+    }
+
+    public V getDest(E directed_edge) {
+        return null;
+    }
+
+    public V getSource(E directed_edge) {
+        return null;
+    }
+
+    public boolean isDest(V vertex, E edge) {
+        return false;
+    }
+
+    public boolean isSource(V vertex, E edge) {
+        return false;
+    }
+
+    public int getEdgeCount() {
+        return edges.size();
+    }
+
+    public int getVertexCount() {
+        return vertices.size();
+    }
+
+}
diff --git a/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/util/TestGraphs.java b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/util/TestGraphs.java
new file mode 100644
index 0000000..e9fb5ed
--- /dev/null
+++ b/jung-graph-impl/src/main/java/edu/uci/ics/jung/graph/util/TestGraphs.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Jul 2, 2003
+ *  
+ */
+package edu.uci.ics.jung.graph.util;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.UndirectedSparseMultigraph;
+
+/**
+ * Provides generators for several different test graphs.
+ */
+public class TestGraphs {
+
+	/**
+	 * A series of pairs that may be useful for generating graphs. The
+	 * miniature graph consists of 8 edges, 10 nodes, and is formed of two
+	 * connected components, one of 8 nodes, the other of 2.
+	 *  
+	 */
+	public static String[][] pairs = { { "a", "b", "3" }, {
+			"a", "c", "4" }, {
+			"a", "d", "5" }, {
+			"d", "c", "6" }, {
+			"d", "e", "7" }, {
+			"e", "f", "8" }, {
+			"f", "g", "9" }, {
+			"h", "i", "1" }
+	};
+
+	/**
+	 * Creates a small sample graph that can be used for testing purposes. The
+	 * graph is as described in the section on {@link #pairs pairs}. If <code>isDirected</code>,
+	 * the graph is a {@link DirectedSparseMultigraph DirectedSparseMultigraph},
+	 * otherwise, it is an {@link UndirectedSparseMultigraph UndirectedSparseMultigraph}.
+	 * 
+	 * @param directed true iff the graph created is to have directed edges
+	 * @return a graph consisting of eight edges and ten nodes.
+	 */
+	public static Graph<String, Number> createTestGraph(boolean directed) {
+		Graph<String, Number> graph = null;
+		if(directed) {
+			graph = new DirectedSparseMultigraph<String,Number>();
+		} else {
+			graph = new UndirectedSparseMultigraph<String,Number>();
+		}
+
+		for (int i = 0; i < pairs.length; i++) {
+			String[] pair = pairs[i];
+			graph.addEdge(Integer.parseInt(pair[2]), pair[0], pair[1]);
+		}
+		return graph;
+	}
+
+    /**
+     * @param chain_length the length of the chain of vertices to add to the returned graph
+     * @param isolate_count the number of isolated vertices to add to the returned graph
+     * @return a graph consisting of a chain of {@code chain_length} vertices
+     *     and {@code isolate_count} isolated vertices.
+     */
+    public static Graph<String,Number> createChainPlusIsolates(int chain_length, int isolate_count)
+    {
+    	Graph<String,Number> g = new UndirectedSparseMultigraph<String,Number>();
+        if (chain_length > 0)
+        {
+            String[] v = new String[chain_length];
+            v[0] = "v"+0;
+            g.addVertex(v[0]);
+            for (int i = 1; i < chain_length; i++)
+            {
+                v[i] = "v"+i;
+                g.addVertex(v[i]);
+                g.addEdge(new Double(Math.random()), v[i], v[i-1]);
+            }
+        }
+        for (int i = 0; i < isolate_count; i++) {
+            String v = "v"+(chain_length+i);
+            g.addVertex(v);
+        }
+        return g;
+    }
+    
+	/**
+	 * Creates a sample directed acyclic graph by generating several "layers",
+	 * and connecting nodes (randomly) to nodes in earlier (but never later)
+	 * layers.  The number of vertices in each layer is a random value in the range
+	 * [1, maxNodesPerLayer].
+	 * 
+	 * @param layers the number of layers of vertices to create in the graph
+	 * @param maxNodesPerLayer the maximum number of vertices to put in any layer
+	 * @param linkprob the probability that this method will add an edge from a vertex in layer
+	 *     <i>k</i> to a vertex in layer <i>k+1</i>
+	 * @return the created graph
+	 */
+	public static Graph<String,Number> createDirectedAcyclicGraph(
+		int layers,
+		int maxNodesPerLayer,
+		double linkprob) {
+
+		DirectedGraph<String,Number> dag = new DirectedSparseMultigraph<String,Number>();
+		Set<String> previousLayers = new HashSet<String>();
+		Set<String> inThisLayer = new HashSet<String>();
+		for (int i = 0; i < layers; i++) {
+
+			int nodesThisLayer = (int) (Math.random() * maxNodesPerLayer) + 1;
+			for (int j = 0; j < nodesThisLayer; j++) {
+                String v = i+":"+j;
+				dag.addVertex(v);
+				inThisLayer.add(v);
+				// for each previous node...
+                for(String v2 : previousLayers) {
+					if (Math.random() < linkprob) {
+                        Double de = new Double(Math.random());
+						dag.addEdge(de, v, v2);
+					}
+				}
+			}
+
+			previousLayers.addAll(inThisLayer);
+			inThisLayer.clear();
+		}
+		return dag;
+	}
+	
+	private static void createEdge(
+			Graph<String, Number> g,
+			String v1Label,
+			String v2Label,
+			int weight) {
+			g.addEdge(new Double(Math.random()), v1Label, v2Label);
+	}
+	
+	/**
+	 * Returns a bigger, undirected test graph with a just one component. This
+	 * graph consists of a clique of ten edges, a partial clique (randomly
+	 * generated, with edges of 0.6 probability), and one series of edges
+	 * running from the first node to the last.
+	 * 
+	 * @return the testgraph
+	 */
+	public static Graph<String,Number> getOneComponentGraph() {
+
+		UndirectedGraph<String,Number> g = new UndirectedSparseMultigraph<String,Number>();
+		// let's throw in a clique, too
+		for (int i = 1; i <= 10; i++) {
+			for (int j = i + 1; j <= 10; j++) {
+				String i1 = "" + i;
+				String i2 = "" + j;
+				g.addEdge(Math.pow(i+2,j), i1, i2);
+			}
+		}
+
+		// and, last, a partial clique
+		for (int i = 11; i <= 20; i++) {
+			for (int j = i + 1; j <= 20; j++) {
+				if (Math.random() > 0.6)
+					continue;
+				String i1 = "" + i;
+				String i2 = "" + j;
+				g.addEdge(Math.pow(i+2,j), i1, i2);
+			}
+		}
+
+		List<String> index = new ArrayList<String>();
+		index.addAll(g.getVertices());
+		// and one edge to connect them all
+		for (int i = 0; i < index.size() - 1; i++) 
+		    g.addEdge(new Integer(i), index.get(i), index.get(i+1));
+
+		return g;
+	}
+
+	/**
+	 * Returns a bigger test graph with a clique, several components, and other
+	 * parts.
+	 * 
+	 * @return a demonstration graph of type <tt>UndirectedSparseMultigraph</tt>
+	 *         with 28 vertices.
+	 */
+	public static Graph<String, Number> getDemoGraph() {
+		UndirectedGraph<String, Number> g = 
+            new UndirectedSparseMultigraph<String, Number>();
+
+		for (int i = 0; i < pairs.length; i++) {
+			String[] pair = pairs[i];
+			createEdge(g, pair[0], pair[1], Integer.parseInt(pair[2]));
+		}
+
+		// let's throw in a clique, too
+		for (int i = 1; i <= 10; i++) {
+			for (int j = i + 1; j <= 10; j++) {
+				String i1 = "c" + i;
+				String i2 = "c" + j;
+                g.addEdge(Math.pow(i+2,j), i1, i2);
+			}
+		}
+
+		// and, last, a partial clique
+		for (int i = 11; i <= 20; i++) {
+			for (int j = i + 1; j <= 20; j++) {
+				if (Math.random() > 0.6)
+					continue;
+				String i1 = "p" + i;
+				String i2 = "p" + j;
+                g.addEdge(Math.pow(i+2,j), i1, i2);
+			}
+		}
+		return g;
+	}
+
+    /**
+     * @return a small graph with directed and undirected edges, and parallel edges.
+     */
+    public static Graph<String, Number> getSmallGraph() {
+        Graph<String, Number> graph = 
+            new SparseMultigraph<String, Number>();
+        String[] v = new String[3];
+        for (int i = 0; i < 3; i++) {
+            v[i] = String.valueOf(i);
+            graph.addVertex(v[i]);
+        }
+        graph.addEdge(new Double(0), v[0], v[1], EdgeType.DIRECTED);
+        graph.addEdge(new Double(.1), v[0], v[1], EdgeType.DIRECTED);
+        graph.addEdge(new Double(.2), v[0], v[1], EdgeType.DIRECTED);
+        graph.addEdge(new Double(.3), v[1], v[0], EdgeType.DIRECTED);
+        graph.addEdge(new Double(.4), v[1], v[0], EdgeType.DIRECTED);
+        graph.addEdge(new Double(.5), v[1], v[2]);
+        graph.addEdge(new Double(.6), v[1], v[2]);
+
+        return graph;
+    }
+}
diff --git a/jung-graph-impl/src/site/site.xml b/jung-graph-impl/src/site/site.xml
new file mode 100644
index 0000000..4f25bdf
--- /dev/null
+++ b/jung-graph-impl/src/site/site.xml
@@ -0,0 +1,14 @@
+<project name="${project.name}">
+  <bannerLeft>
+    <name>${project.name}</name>
+  </bannerLeft>
+  <body>
+    <links>
+      <item name="${project.name}" href="${project.url}"/>
+    </links>
+    <menu ref="parent" />
+    <menu ref="modules" />
+    <menu ref="reports" />
+  </body>
+</project>
+
diff --git a/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/DirectedSparseMultigraphTest.java b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/DirectedSparseMultigraphTest.java
new file mode 100644
index 0000000..a497e5b
--- /dev/null
+++ b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/DirectedSparseMultigraphTest.java
@@ -0,0 +1,20 @@
+package edu.uci.ics.jung.graph;
+
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+
+public class DirectedSparseMultigraphTest 
+	extends AbstractDirectedSparseMultigraphTest {
+
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        graph = new DirectedSparseMultigraph<Integer,Number>();
+        graph.addEdge(e01, v0, v1);
+        graph.addEdge(e10, v1, v0);
+        graph.addEdge(e12, v1, v2);
+        graph.addEdge(e21, v2, v1);
+
+    }
+
+}
diff --git a/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/HypergraphTest.java b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/HypergraphTest.java
new file mode 100644
index 0000000..0f2bd6d
--- /dev/null
+++ b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/HypergraphTest.java
@@ -0,0 +1,55 @@
+/*
+ * Created on Apr 21, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.graph;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import com.google.common.base.Supplier;
+
+
+public class HypergraphTest extends AbstractHypergraphTest
+{
+    
+    public HypergraphTest(Supplier<? extends Hypergraph<Integer,Character>> factory)
+    {
+        super(factory);
+    }
+    
+    @Override
+    public void setUp()
+    {
+        h = factory.get();
+//        System.out.println(h.getClass().getSimpleName());
+    }
+    
+    public static Test suite()
+    {
+        TestSuite ts = new TestSuite("HypergraphTest");
+        
+        ts.addTest(new HypergraphTest(SetHypergraph.<Integer,Character>getFactory()));
+        ts.addTest(new HypergraphTest(DirectedOrderedSparseMultigraph.<Integer,Character>getFactory()));
+        ts.addTest(new HypergraphTest(DirectedSparseGraph.<Integer,Character>getFactory()));
+        ts.addTest(new HypergraphTest(DirectedSparseMultigraph.<Integer,Character>getFactory()));
+        ts.addTest(new HypergraphTest(OrderedSparseMultigraph.<Integer,Character>getFactory()));
+        ts.addTest(new HypergraphTest(SortedSparseMultigraph.<Integer,Character>getFactory()));
+        ts.addTest(new HypergraphTest(SparseGraph.<Integer,Character>getFactory()));
+        ts.addTest(new HypergraphTest(SparseMultigraph.<Integer,Character>getFactory()));
+        ts.addTest(new HypergraphTest(UndirectedOrderedSparseMultigraph.<Integer,Character>getFactory()));
+        ts.addTest(new HypergraphTest(UndirectedSparseGraph.<Integer,Character>getFactory()));
+        ts.addTest(new HypergraphTest(UndirectedSparseMultigraph.<Integer,Character>getFactory()));
+//        ts.addTest(new HypergraphTest(.getFactory()));
+        
+        return ts;
+    }
+    
+}
diff --git a/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/OrderedSparseMultigraphTest.java b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/OrderedSparseMultigraphTest.java
new file mode 100644
index 0000000..0e878cf
--- /dev/null
+++ b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/OrderedSparseMultigraphTest.java
@@ -0,0 +1,49 @@
+package edu.uci.ics.jung.graph;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import edu.uci.ics.jung.graph.OrderedSparseMultigraph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+public class OrderedSparseMultigraphTest extends AbstractOrderedSparseMultigraphTest {
+
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Set<Number> seeds = new HashSet<Number>();
+        seeds.add(1);
+        seeds.add(5);
+        graph = new OrderedSparseMultigraph<Number,Number>();
+        graph.addEdge(4, 2, 1);
+        graph.addEdge(5, 3, 1);
+        graph.addEdge(6, 0, 4, EdgeType.DIRECTED);
+        graph.addEdge(7, 0, 5, EdgeType.DIRECTED);
+        graph.addEdge(1, 0, 1);
+        graph.addEdge(2, 1, 2);
+        graph.addEdge(3, 0, 2);
+        graph.addEdge(8, 5, 1, EdgeType.DIRECTED);
+        graph.addEdge(9, 6, 1, EdgeType.DIRECTED);
+        graph.addEdge(10, 4, 3, EdgeType.DIRECTED);
+        graph.addEdge(16, 8, 3);
+        graph.addEdge(17, 5, 7);
+        graph.addEdge(11, 2, 7);
+        graph.addEdge(12, 1, 5);
+        graph.addEdge(13, 2, 6);
+        graph.addEdge(14, 6, 4);
+        graph.addEdge(15, 7, 8);
+
+        smallGraph = new SparseMultigraph<Integer,Number>();
+        smallGraph.addVertex(v0);
+        smallGraph.addVertex(v1);
+        smallGraph.addVertex(v2);
+        smallGraph.addEdge(e01, v0, v1);
+        smallGraph.addEdge(e10, v1, v0);
+        smallGraph.addEdge(e12, v1, v2);
+        smallGraph.addEdge(e21, v2, v1, EdgeType.DIRECTED);
+
+    }
+
+}
diff --git a/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SortedSparseMultigraphTest.java b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SortedSparseMultigraphTest.java
new file mode 100644
index 0000000..eb5d7dc
--- /dev/null
+++ b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SortedSparseMultigraphTest.java
@@ -0,0 +1,57 @@
+package edu.uci.ics.jung.graph;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+public class SortedSparseMultigraphTest 
+	extends AbstractSortedSparseMultigraphTest {
+
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Set<Number> seeds = new HashSet<Number>();
+        seeds.add(1);
+        seeds.add(5);
+        graph = new SortedSparseMultigraph<Integer,Double>();
+        graph.addEdge(4., 2, 1);
+        graph.addEdge(5., 3, 1);
+        graph.addEdge(6., 0, 4, EdgeType.DIRECTED);
+        graph.addEdge(7., 0, 5, EdgeType.DIRECTED);
+        graph.addEdge(1., 0, 1);
+        graph.addEdge(2., 1, 2);
+        graph.addEdge(3., 0, 2);
+        graph.addEdge(8., 5, 1, EdgeType.DIRECTED);
+        graph.addEdge(9., 6, 1, EdgeType.DIRECTED);
+        graph.addEdge(10., 4, 3, EdgeType.DIRECTED);
+        graph.addEdge(16., 8, 3);
+        graph.addEdge(17., 5, 7);
+        graph.addEdge(11., 2, 7);
+        graph.addEdge(12., 1, 5);
+        graph.addEdge(13., 2, 6);
+        graph.addEdge(14., 6, 4);
+        graph.addEdge(15., 7, 8);
+
+        smallGraph = new SparseMultigraph<Integer,Double>();
+        smallGraph.addVertex(v0);
+        smallGraph.addVertex(v1);
+        smallGraph.addVertex(v2);
+        smallGraph.addEdge(e01, v0, v1);
+        smallGraph.addEdge(e10, v1, v0);
+        smallGraph.addEdge(e12, v1, v2);
+        smallGraph.addEdge(e21, v2, v1, EdgeType.DIRECTED);
+        
+        Graph<Foo,Bar> fooBar = new SortedSparseMultigraph<Foo,Bar>();
+        try {
+        	fooBar.addVertex(new Foo());
+        	fooBar.addVertex(new Foo());
+        	fooBar.addEdge(new Bar(), new Foo(), new Foo());
+        	fail("should have thrown an exception as Foo Bar are not Comparable");
+        } catch(Exception ex) {
+        	// all is well
+        }
+
+    }
+}
diff --git a/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SparseGraphAddRemoveAddEdgeTest.java b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SparseGraphAddRemoveAddEdgeTest.java
new file mode 100644
index 0000000..09c287c
--- /dev/null
+++ b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SparseGraphAddRemoveAddEdgeTest.java
@@ -0,0 +1,25 @@
+package edu.uci.ics.jung.graph;
+
+import junit.framework.TestCase;
+
+
+public class SparseGraphAddRemoveAddEdgeTest extends TestCase {
+	
+    SparseGraph<String,Integer> g;
+
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        g = new SparseGraph<String,Integer>();
+    }
+    
+    public void testAddRemoveAddEdge() {
+        g.addVertex("A"); g.addVertex("B");
+        g.addEdge(1, "A", "B");
+        g.removeEdge(1);  // Remove the edge between A and B
+        g.addEdge(2, "A", "B"); // Then add a different edge -> Exception thrown
+
+    }
+
+}
diff --git a/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SparseMultigraphTest.java b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SparseMultigraphTest.java
new file mode 100644
index 0000000..aa2d21d
--- /dev/null
+++ b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SparseMultigraphTest.java
@@ -0,0 +1,49 @@
+package edu.uci.ics.jung.graph;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import edu.uci.ics.jung.graph.SparseMultigraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+public class SparseMultigraphTest 
+	extends AbstractSparseMultigraphTest {
+
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Set<Number> seeds = new HashSet<Number>();
+        seeds.add(1);
+        seeds.add(5);
+        graph = new SparseMultigraph<Number,Number>();
+        graph.addEdge(1, 0, 1);
+        graph.addEdge(2, 1, 2);
+        graph.addEdge(3, 0, 2);
+        graph.addEdge(4, 2, 1);
+        graph.addEdge(5, 3, 1);
+        graph.addEdge(6, 0, 4, EdgeType.DIRECTED);
+        graph.addEdge(7, 0, 5, EdgeType.DIRECTED);
+        graph.addEdge(8, 5, 1, EdgeType.DIRECTED);
+        graph.addEdge(9, 6, 1, EdgeType.DIRECTED);
+        graph.addEdge(10, 4, 3, EdgeType.DIRECTED);
+        graph.addEdge(11, 2, 7);
+        graph.addEdge(12, 1, 5);
+        graph.addEdge(13, 2, 6);
+        graph.addEdge(14, 6, 4);
+        graph.addEdge(15, 7, 8);
+        graph.addEdge(16, 8, 3);
+        graph.addEdge(17, 5, 7);
+
+        smallGraph = new SparseMultigraph<Integer,Number>();
+        smallGraph.addVertex(v0);
+        smallGraph.addVertex(v1);
+        smallGraph.addVertex(v2);
+        smallGraph.addEdge(e01, v0, v1);
+        smallGraph.addEdge(e10, v1, v0);
+        smallGraph.addEdge(e12, v1, v2);
+        smallGraph.addEdge(e21, v2, v1, EdgeType.DIRECTED);
+
+    }
+
+}
diff --git a/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SparseTreeTest.java b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SparseTreeTest.java
new file mode 100644
index 0000000..08601f0
--- /dev/null
+++ b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/SparseTreeTest.java
@@ -0,0 +1,21 @@
+package edu.uci.ics.jung.graph;
+
+import com.google.common.base.Supplier;
+
+public class SparseTreeTest extends AbstractSparseTreeTest {
+
+	/* (non-Javadoc)
+	 * @see junit.framework.TestCase#setUp()
+	 */
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+		graphFactory = DirectedSparseMultigraph.<String,Integer>getFactory();
+		edgeFactory = new Supplier<Integer>() {
+			int i=0;
+			public Integer get() {
+				return i++;
+			}};
+		tree = new DelegateTree<String,Integer>(graphFactory);
+	}
+}
diff --git a/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/TreeUtilsTest.java b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/TreeUtilsTest.java
new file mode 100644
index 0000000..b0bbc8f
--- /dev/null
+++ b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/TreeUtilsTest.java
@@ -0,0 +1,23 @@
+package edu.uci.ics.jung.graph;
+
+import edu.uci.ics.jung.graph.DelegateTree;
+
+public class TreeUtilsTest extends AbstractTreeUtilsTest {
+	
+	/**
+	 * @see junit.framework.TestCase#setUp()
+	 */
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+		tree = new DelegateTree<String,Integer>();
+		tree.addVertex("A");
+		tree.addEdge(1, "A", "B0");
+		tree.addEdge(2, "A", "B1");
+		tree.addEdge(3, "B0", "C0");
+		tree.addEdge(4, "C0", "D0");
+		tree.addEdge(5, "C0", "D1");
+	}
+	
+
+}
diff --git a/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/UndirectedSparseMultigraphTest.java b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/UndirectedSparseMultigraphTest.java
new file mode 100644
index 0000000..3f986a2
--- /dev/null
+++ b/jung-graph-impl/src/test/java/edu/uci/ics/jung/graph/UndirectedSparseMultigraphTest.java
@@ -0,0 +1,18 @@
+package edu.uci.ics.jung.graph;
+
+import edu.uci.ics.jung.graph.UndirectedSparseMultigraph;
+
+public class UndirectedSparseMultigraphTest 
+	extends AbstractUndirectedSparseMultigraphTest {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        graph = new UndirectedSparseMultigraph<Integer,Number>();
+        graph.addEdge(e01, v0, v1);
+        graph.addEdge(e10, v1, v0);
+        graph.addEdge(e12, v1, v2);
+        graph.addEdge(e21, v2, v1);
+
+    }
+}
diff --git a/jung-io-2.0.1-sources.jar b/jung-io-2.0.1-sources.jar
deleted file mode 100644
index 161d0f0..0000000
Binary files a/jung-io-2.0.1-sources.jar and /dev/null differ
diff --git a/jung-io/pom.xml b/jung-io/pom.xml
new file mode 100644
index 0000000..4d55454
--- /dev/null
+++ b/jung-io/pom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>net.sf.jung</groupId>
+    <artifactId>jung-parent</artifactId>
+    <version>2.1.1</version>
+  </parent>
+  <artifactId>jung-io</artifactId>
+  <name>JUNG - I/O Support</name>
+  <description>IO support classes for JUNG</description>
+
+  <dependencies>
+    <dependency>
+      <groupId>net.sf.jung</groupId>
+      <artifactId>jung-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>net.sf.jung</groupId>
+      <artifactId>jung-algorithms</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>net.sf.jung</groupId>
+      <artifactId>jung-graph-impl</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/GraphFile.java b/jung-io/src/main/java/edu/uci/ics/jung/io/GraphFile.java
new file mode 100644
index 0000000..4d669dc
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/GraphFile.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Jan 6, 2002
+ *
+ */
+package edu.uci.ics.jung.io;
+
+import edu.uci.ics.jung.graph.Graph;
+
+
+/**
+ * General interface for loading and saving a graph from/to disk.
+ * @author Scott
+ * @author Tom Nelson - converted to jung2
+ *
+ */
+public interface GraphFile<V,E> {
+	
+	/**
+	 * Loads a graph from a file per the appropriate format
+	 * @param filename the location and name of the file
+	 * @return the graph
+	 */
+	Graph<V,E> load(String filename);
+	
+	/**
+	 * Save a graph to disk per the appropriate format
+	 * @param graph the location and name of the file
+	 * @param filename the graph
+	 */
+	void save(Graph<V,E> graph, String filename);
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/GraphIOException.java b/jung-io/src/main/java/edu/uci/ics/jung/io/GraphIOException.java
new file mode 100644
index 0000000..b1914c5
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/GraphIOException.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io;
+
+/**
+ * Exception thrown when IO errors occur when reading/writing graphs.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class GraphIOException extends Exception {
+
+    private static final long serialVersionUID = 3773882099782535606L;
+
+    /**
+     * Creates a new instance with no specified message or cause.
+     */
+    public GraphIOException() {
+        super();
+    }
+
+    /**
+     * Creates a new instance with the specified message and cause.
+     * @param message a description of the exception-triggering event
+     * @param cause the exception which triggered this one
+     */
+    public GraphIOException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * Creates a new instance with the specified message and no
+     * specified cause.
+     * @param message a description of the exception-triggering event
+     */
+    public GraphIOException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creats a new instance with the specified cause and no specified message.
+     * @param cause the exception which triggered this one
+     */
+    public GraphIOException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/GraphMLMetadata.java b/jung-io/src/main/java/edu/uci/ics/jung/io/GraphMLMetadata.java
new file mode 100644
index 0000000..4212b6a
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/GraphMLMetadata.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Jun 30, 2008
+ * 
+ */
+package edu.uci.ics.jung.io;
+
+import com.google.common.base.Function;
+
+/**
+ * Maintains information relating to data for the specified type.
+ * This includes a Function from objects to their values,
+ * a default value, and a description.
+ */
+public class GraphMLMetadata<T> 
+{
+	/**
+	 * The description of this data type.
+	 */
+	public String description;
+	
+	/**
+	 * The default value for objects of this type.
+	 */
+	public String default_value;
+	
+	/**
+	 * A Function mapping objects to string representations of their values.
+	 */
+	public Function<T, String> transformer;
+	
+	/**
+	 * Creates a new instance with the specified description,
+	 * default value, and function.
+	 * 
+	 * @param description a textual description of the object
+	 * @param default_value the default value for the object, as a String
+	 * @param function maps objects of this type to string representations
+	 */
+	public GraphMLMetadata(String description, String default_value,
+			Function<T, String> function)
+	{
+		this.description = description;
+		this.transformer = function;
+		this.default_value = default_value;
+	}
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/GraphMLReader.java b/jung-io/src/main/java/edu/uci/ics/jung/io/GraphMLReader.java
new file mode 100644
index 0000000..c100e1c
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/GraphMLReader.java
@@ -0,0 +1,831 @@
+/*
+ * Created on Sep 21, 2007
+ *
+ * Copyright (c) 2007, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.io;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+
+import edu.uci.ics.jung.algorithms.util.MapSettableTransformer;
+import edu.uci.ics.jung.algorithms.util.SettableTransformer;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * Reads in data from a GraphML-formatted file and generates graphs based on
+ * that data.  Currently supports the following parts of the GraphML
+ * specification:
+ * <ul>
+ * <li>graphs and hypergraphs
+ * <li>directed and undirected edges
+ * <li>graph, vertex, edge <code>data</code>
+ * <li>graph, vertex, edge descriptions and <code>data</code> descriptions
+ * <li>vertex and edge IDs
+ * </ul>
+ * Each of these is exposed via appropriate <code>get</code> methods.
+ *
+ * Does not currently support nested graphs or ports.
+ *
+ * <p>Note that the user is responsible for supplying a graph
+ * <code>Factory</code> that can support the edge types in the supplied
+ * GraphML file.  If the graph generated by the <code>Factory</code> is
+ * not compatible (for example: if the graph does not accept directed
+ * edges, and the GraphML file contains a directed edge) then the results
+ * are graph-implementation-dependent.
+ *
+ * @see "http://graphml.graphdrawing.org/specification.html"
+ */
+public class GraphMLReader<G extends Hypergraph<V,E>, V, E> extends DefaultHandler
+{
+    protected enum TagState {NO_TAG, VERTEX, EDGE, HYPEREDGE, ENDPOINT, GRAPH,
+      DATA, KEY, DESC, DEFAULT_KEY, GRAPHML, OTHER}
+
+    protected enum KeyType {NONE, VERTEX, EDGE, GRAPH, ALL}
+
+    protected SAXParser saxp;
+    protected EdgeType default_edgetype;
+    protected G current_graph;
+    protected V current_vertex;
+    protected E current_edge;
+    protected String current_key;
+    protected LinkedList<TagState> current_states;
+    protected BiMap<String, TagState> tag_state;
+    protected Supplier<G> graph_factory;
+    protected Supplier<V> vertex_factory;
+    protected Supplier<E> edge_factory;
+    protected BiMap<V, String> vertex_ids;
+    protected BiMap<E, String> edge_ids;
+    protected Map<String, GraphMLMetadata<G>> graph_metadata;
+    protected Map<String, GraphMLMetadata<V>> vertex_metadata;
+    protected Map<String, GraphMLMetadata<E>> edge_metadata;
+    protected Map<V, String> vertex_desc;
+    protected Map<E, String> edge_desc;
+    protected Map<G, String> graph_desc;
+    protected KeyType key_type;
+    protected Collection<V> hyperedge_vertices;
+
+    protected List<G> graphs;
+
+    protected StringBuilder current_text = new StringBuilder();
+    
+    /**
+     * Creates a <code>GraphMLReader</code> instance with the specified
+     * vertex and edge factories.
+     *
+     * @param vertex_factory the vertex supplier to use to create vertex objects
+     * @param edge_factory the edge supplier to use to create edge objects
+     * @throws ParserConfigurationException if a SAX parser cannot be constructed
+     * @throws SAXException if the SAX parser factory cannot be constructed
+     */
+    public GraphMLReader(Supplier<V> vertex_factory,
+    		Supplier<E> edge_factory)
+        throws ParserConfigurationException, SAXException
+    {
+        current_vertex = null;
+        current_edge = null;
+
+        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
+        saxp = saxParserFactory.newSAXParser();
+
+        current_states = new LinkedList<TagState>();
+
+        tag_state = HashBiMap.<String, TagState>create();
+        tag_state.put("node", TagState.VERTEX);
+        tag_state.put("edge", TagState.EDGE);
+        tag_state.put("hyperedge", TagState.HYPEREDGE);
+        tag_state.put("endpoint", TagState.ENDPOINT);
+        tag_state.put("graph", TagState.GRAPH);
+        tag_state.put("data", TagState.DATA);
+        tag_state.put("key", TagState.KEY);
+        tag_state.put("desc", TagState.DESC);
+        tag_state.put("default", TagState.DEFAULT_KEY);
+        tag_state.put("graphml", TagState.GRAPHML);
+
+        this.key_type = KeyType.NONE;
+
+        this.vertex_factory = vertex_factory;
+        this.edge_factory = edge_factory;
+    }
+
+    /**
+     * Creates a <code>GraphMLReader</code> instance that assigns the vertex
+     * and edge <code>id</code> strings to be the vertex and edge objects,
+     * as well as their IDs.
+     * Note that this requires that (a) each edge have a valid ID, which is not
+     * normally a requirement for edges in GraphML, and (b) that the vertex
+     * and edge types be assignment-compatible with <code>String</code>.
+     * @throws ParserConfigurationException if a SAX parser cannot be constructed
+     * @throws SAXException if the SAX parser factory cannot be constructed
+     */
+    public GraphMLReader() throws ParserConfigurationException, SAXException
+    {
+    	this(null, null);
+    }
+
+    /**
+     * Returns a list of the graphs parsed from the specified reader, as created by
+     * the specified Supplier.
+     * @param reader the source of the graph data in GraphML format
+     * @param graph_factory used to build graph instances
+     * @return the graphs parsed from the specified reader
+     * @throws IOException if an error is encountered while parsing the graph
+     */
+    public List<G> loadMultiple(Reader reader, Supplier<G> graph_factory)
+        throws IOException
+    {
+        this.graph_factory = graph_factory;
+        initializeData();
+        clearData();
+        parse(reader);
+
+        return graphs;
+    }
+
+    /**
+     * Returns a list of the graphs parsed from the specified file, as created by
+     * the specified Supplier.
+     * @param filename the source of the graph data in GraphML format
+     * @param graph_factory used to build graph instances
+     * @return the graphs parsed from the specified file
+     * @throws IOException if an error is encountered while parsing the graph
+     */
+    public List<G> loadMultiple(String filename, Supplier<G> graph_factory) throws IOException
+    {
+        return loadMultiple(new FileReader(filename), graph_factory);
+    }
+
+    /**
+     * Populates the specified graph with the data parsed from the reader.
+     * @param reader the source of the graph data in GraphML format
+     * @param g the graph instance to populate
+     * @throws IOException if an error is encountered while parsing the graph
+     */
+    public void load(Reader reader, G g) throws IOException
+    {
+        this.current_graph = g;
+        this.graph_factory = null;
+        initializeData();
+        clearData();
+
+        parse(reader);
+    }
+
+    /**
+     * Populates the specified graph with the data parsed from the specified file.
+     * @param filename the source of the graph data in GraphML format
+     * @param g the graph instance to populate
+     * @throws IOException if an error is encountered while parsing the graph
+     */
+    public void load(String filename, G g) throws IOException
+    {
+        load(new FileReader(filename), g);
+    }
+
+    protected void clearData()
+    {
+        this.vertex_ids.clear();
+        this.vertex_desc.clear();
+
+        this.edge_ids.clear();
+        this.edge_desc.clear();
+
+        this.graph_desc.clear();
+
+        this.hyperedge_vertices.clear();
+    }
+
+    /**
+     * This is separate from initialize() because these data structures are shared among all
+     * graphs loaded (i.e., they're defined inside <code>graphml</code> rather than <code>graph</code>.
+     */
+    protected void initializeData()
+    {
+        this.vertex_ids = HashBiMap.<V, String>create();
+        this.vertex_desc = new HashMap<V, String>();
+        this.vertex_metadata = new HashMap<String, GraphMLMetadata<V>>();
+
+        this.edge_ids = HashBiMap.<E, String>create();
+        this.edge_desc = new HashMap<E, String>();
+        this.edge_metadata = new HashMap<String, GraphMLMetadata<E>>();
+
+        this.graph_desc = new HashMap<G, String>();
+        this.graph_metadata = new HashMap<String, GraphMLMetadata<G>>();
+
+        this.hyperedge_vertices = new ArrayList<V>();
+    }
+
+    protected void parse(Reader reader) throws IOException
+    {
+        try
+        {
+            saxp.parse(new InputSource(reader), this);
+            reader.close();
+        }
+        catch (SAXException saxe)
+        {
+            throw new IOException(saxe.getMessage());
+        }
+    }
+
+    @Override
+    public void startElement(String uri, String name, String qName, Attributes atts) throws SAXNotSupportedException
+    {
+        String tag = qName.toLowerCase();
+        TagState state = tag_state.get(tag);
+        if (state == null)
+            state = TagState.OTHER;
+
+        switch (state)
+        {
+            case GRAPHML:
+                break;
+
+            case VERTEX:
+                if (this.current_graph == null)
+                    throw new SAXNotSupportedException("Graph must be defined prior to elements");
+                if (this.current_edge != null || this.current_vertex != null)
+                    throw new SAXNotSupportedException("Nesting elements not supported");
+
+                createVertex(atts);
+
+                break;
+
+            case ENDPOINT:
+                if (this.current_graph == null)
+                    throw new SAXNotSupportedException("Graph must be defined prior to elements");
+                if (this.current_edge == null)
+                    throw new SAXNotSupportedException("No edge defined for endpoint");
+                if (this.current_states.getFirst() != TagState.HYPEREDGE)
+                    throw new SAXNotSupportedException("Endpoints must be defined inside hyperedge");
+                Map<String, String> endpoint_atts = getAttributeMap(atts);
+                String node = endpoint_atts.remove("node");
+                if (node == null)
+                    throw new SAXNotSupportedException("Endpoint must include an 'id' attribute");
+                V v = vertex_ids.inverse().get(node);
+                if (v == null)
+                    throw new SAXNotSupportedException("Endpoint refers to nonexistent node ID: " + node);
+
+                this.current_vertex = v;
+                hyperedge_vertices.add(v);
+                break;
+
+            case EDGE:
+            case HYPEREDGE:
+                if (this.current_graph == null)
+                    throw new SAXNotSupportedException("Graph must be defined prior to elements");
+                if (this.current_edge != null || this.current_vertex != null)
+                    throw new SAXNotSupportedException("Nesting elements not supported");
+
+                createEdge(atts, state);
+                break;
+
+            case GRAPH:
+                if (this.current_graph != null && graph_factory != null)
+                    throw new SAXNotSupportedException("Nesting graphs not currently supported");
+
+                // graph Supplier is null if there's only one graph
+                if (graph_factory != null)
+                    current_graph = graph_factory.get();
+
+                // reset all non-key data structures (to avoid collisions between different graphs)
+                clearData();
+
+                // set up default direction of edges
+                Map<String, String> graph_atts = getAttributeMap(atts);
+                String default_direction = graph_atts.remove("edgedefault");
+                if (default_direction == null)
+                    throw new SAXNotSupportedException("All graphs must specify a default edge direction");
+                if (default_direction.equals("directed"))
+                    this.default_edgetype = EdgeType.DIRECTED;
+                else if (default_direction.equals("undirected"))
+                    this.default_edgetype = EdgeType.UNDIRECTED;
+                else
+                    throw new SAXNotSupportedException("Invalid or unrecognized default edge direction: " + default_direction);
+
+                // put remaining attribute/value pairs in graph_data
+            	addExtraData(graph_atts, graph_metadata, current_graph);
+
+                break;
+
+            case DATA:
+                if (this.current_states.contains(TagState.DATA))
+                    throw new SAXNotSupportedException("Nested data not supported");
+                handleData(atts);
+                break;
+
+            case KEY:
+                createKey(atts);
+                break;
+
+
+            default:
+                break;
+        }
+
+        current_states.addFirst(state);
+    }
+
+    /**
+     * 
+     * @param <T>
+     * @param atts
+     * @param metadata_map
+     * @param current_elt
+     */
+	private <T> void addExtraData(Map<String, String> atts,
+			Map<String, GraphMLMetadata<T>> metadata_map, T current_elt)
+	{
+		// load in the default values; these override anything that might
+		// be in the attribute map (because that's not really a proper
+		// way to associate data)
+        for (Map.Entry<String, GraphMLMetadata<T>> entry: metadata_map.entrySet())
+        {
+        	GraphMLMetadata<T> gmlm = entry.getValue();
+        	if (gmlm.default_value != null)
+        	{
+            	SettableTransformer<T, String> st =
+            		(SettableTransformer<T, String>)gmlm.transformer;
+        		st.set(current_elt, gmlm.default_value);
+        	}
+        }
+
+        // place remaining items in data
+        for (Map.Entry<String, String> entry : atts.entrySet())
+        {
+			String key = entry.getKey();
+			GraphMLMetadata<T> key_data = metadata_map.get(key);
+			SettableTransformer<T, String> st;
+			if (key_data != null)
+			{
+				// if there's a default value, don't override it
+				if (key_data.default_value != null)
+					continue;
+				st = (SettableTransformer<T, String>)key_data.transformer;
+			}
+			else
+			{
+				st = new MapSettableTransformer<T, String>(
+							new HashMap<T, String>());
+				key_data = new GraphMLMetadata<T>(null, null, st);
+				metadata_map.put(key, key_data);
+			}
+			st.set(current_elt, entry.getValue());
+        }
+	}
+
+
+    @Override
+    public void characters(char[] ch, int start, int length) throws SAXNotSupportedException
+    {
+        this.current_text.append(new String(ch, start, length));
+    }
+
+
+    protected <T>void addDatum(Map<String, GraphMLMetadata<T>> metadata,
+    		T current_elt, String text) throws SAXNotSupportedException
+    {
+        if (metadata.containsKey(this.current_key))
+        {
+        	SettableTransformer<T, String> st =
+        		(SettableTransformer<T, String>)(metadata.get(this.current_key).transformer);
+            st.set(current_elt, text);
+        }
+        else
+            throw new SAXNotSupportedException("key " + this.current_key +
+            		" not valid for element " + current_elt);
+    }
+
+    @Override
+    public void endElement(String uri, String name, String qName) throws SAXNotSupportedException
+    {
+        String text = current_text.toString().trim();
+        current_text.setLength(0);
+        
+        String tag = qName.toLowerCase();
+        TagState state = tag_state.get(tag);
+        if (state == null)
+            state = TagState.OTHER;
+        if (state == TagState.OTHER)
+            return;
+
+        if (state != current_states.getFirst())
+            throw new SAXNotSupportedException("Unbalanced tags: opened " +
+            		tag_state.inverse().get(current_states.getFirst()) +
+                    ", closed " + tag);
+
+        switch(state)
+        {
+            case VERTEX:
+            case ENDPOINT:
+                current_vertex = null;
+                break;
+
+            case EDGE:
+                current_edge = null;
+                break;
+
+            case HYPEREDGE:
+                current_graph.addEdge(current_edge, hyperedge_vertices);
+                hyperedge_vertices.clear();
+                current_edge = null;
+                break;
+
+            case GRAPH:
+                current_graph = null;
+                break;
+
+            case KEY:
+                current_key = null;
+                break;
+
+            case DESC:
+                switch (this.current_states.get(1)) // go back one
+                {
+                    case GRAPH:
+                        graph_desc.put(current_graph, text);
+                        break;
+                    case VERTEX:
+                    case ENDPOINT:
+                        vertex_desc.put(current_vertex, text);
+                        break;
+                    case EDGE:
+                    case HYPEREDGE:
+                        edge_desc.put(current_edge, text);
+                        break;
+                    case DATA:
+                        switch (key_type)
+                        {
+                            case GRAPH:
+                                graph_metadata.get(current_key).description = text;
+                                break;
+                            case VERTEX:
+                                vertex_metadata.get(current_key).description = text;
+                                break;
+                            case EDGE:
+                                edge_metadata.get(current_key).description = text;
+                                break;
+                            case ALL:
+                                graph_metadata.get(current_key).description = text;
+                                vertex_metadata.get(current_key).description = text;
+                                edge_metadata.get(current_key).description = text;
+                                break;
+                            default:
+                                throw new SAXNotSupportedException("Invalid key type" +
+                                        " specified for default: " + key_type);
+                        }
+
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            case DATA:
+                this.key_type = KeyType.NONE;
+                switch (this.current_states.get(1))
+                {
+                    case GRAPH:
+                        addDatum(graph_metadata, current_graph, text);
+                        break;
+                    case VERTEX:
+                    case ENDPOINT:
+                        addDatum(vertex_metadata, current_vertex, text);
+                        break;
+                    case EDGE:
+                    case HYPEREDGE:
+                        addDatum(edge_metadata, current_edge, text);
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            case DEFAULT_KEY:
+                if (this.current_states.get(1) != TagState.KEY)
+                    throw new SAXNotSupportedException("'default' only defined in context of 'key' tag: " +
+                            "stack: " + current_states.toString());
+
+                switch (key_type)
+                {
+                    case GRAPH:
+                        graph_metadata.get(current_key).default_value = text;
+                        break;
+                    case VERTEX:
+                        vertex_metadata.get(current_key).default_value = text;
+                        break;
+                    case EDGE:
+                        edge_metadata.get(current_key).default_value = text;
+                        break;
+                    case ALL:
+                        graph_metadata.get(current_key).default_value = text;
+                        vertex_metadata.get(current_key).default_value = text;
+                        edge_metadata.get(current_key).default_value = text;
+                        break;
+                    default:
+                        throw new SAXNotSupportedException("Invalid key type" +
+                                " specified for default: " + key_type);
+                }
+
+                break;
+            default:
+                break;
+        }
+
+        current_states.removeFirst();
+    }
+
+    protected Map<String, String> getAttributeMap(Attributes atts)
+    {
+        Map<String,String> att_map = new HashMap<String,String>();
+        for (int i = 0; i < atts.getLength(); i++)
+            att_map.put(atts.getQName(i), atts.getValue(i));
+
+        return att_map;
+    }
+
+    protected void handleData(Attributes atts) throws SAXNotSupportedException
+    {
+        switch (this.current_states.getFirst())
+        {
+            case GRAPH:
+                break;
+            case VERTEX:
+            case ENDPOINT:
+                break;
+            case EDGE:
+                break;
+            case HYPEREDGE:
+                break;
+            default:
+                throw new SAXNotSupportedException("'data' tag only defined " +
+                		"if immediately containing tag is 'graph', 'node', " +
+                        "'edge', or 'hyperedge'");
+        }
+        this.current_key = getAttributeMap(atts).get("key");
+        if (this.current_key == null)
+            throw new SAXNotSupportedException("'data' tag requires a key specification");
+        if (this.current_key.equals(""))
+            throw new SAXNotSupportedException("'data' tag requires a non-empty key");
+        if (!getGraphMetadata().containsKey(this.current_key) &&
+            !getVertexMetadata().containsKey(this.current_key) &&
+            !getEdgeMetadata().containsKey(this.current_key))
+        {
+            throw new SAXNotSupportedException("'data' tag's key specification must reference a defined key");
+        }
+
+    }
+
+    protected void createKey(Attributes atts) throws SAXNotSupportedException
+    {
+        Map<String, String> key_atts = getAttributeMap(atts);
+        String id = key_atts.remove("id");
+        String for_type = key_atts.remove("for");
+
+        if (for_type == null || for_type.equals("") || for_type.equals("all"))
+        {
+            vertex_metadata.put(id,
+            		new GraphMLMetadata<V>(null, null,
+            				new MapSettableTransformer<V, String>(new HashMap<V, String>())));
+            edge_metadata.put(id,
+            		new GraphMLMetadata<E>(null, null,
+            				new MapSettableTransformer<E, String>(new HashMap<E, String>())));
+            graph_metadata.put(id,
+            		new GraphMLMetadata<G>(null, null,
+            				new MapSettableTransformer<G, String>(new HashMap<G, String>())));
+            key_type = KeyType.ALL;
+        }
+        else
+        {
+            TagState type = tag_state.get(for_type);
+            switch (type)
+            {
+                case VERTEX:
+                    vertex_metadata.put(id,
+                    		new GraphMLMetadata<V>(null, null,
+                    				new MapSettableTransformer<V, String>(new HashMap<V, String>())));
+                    key_type = KeyType.VERTEX;
+                    break;
+                case EDGE:
+                case HYPEREDGE:
+                    edge_metadata.put(id,
+                    		new GraphMLMetadata<E>(null, null,
+                    				new MapSettableTransformer<E, String>(new HashMap<E, String>())));
+                    key_type = KeyType.EDGE;
+                    break;
+                case GRAPH:
+                    graph_metadata.put(id,
+                    		new GraphMLMetadata<G>(null, null,
+                    				new MapSettableTransformer<G, String>(new HashMap<G, String>())));
+                    key_type = KeyType.GRAPH;
+                    break;
+                default:
+                	throw new SAXNotSupportedException(
+                			"Invalid metadata target type: " + for_type);
+            }
+        }
+
+        this.current_key = id;
+
+    }
+
+    @SuppressWarnings("unchecked")
+	protected void createVertex(Attributes atts) throws SAXNotSupportedException
+    {
+        Map<String, String> vertex_atts = getAttributeMap(atts);
+        String id = vertex_atts.remove("id");
+        if (id == null)
+            throw new SAXNotSupportedException("node attribute list missing " +
+            		"'id': " + atts.toString());
+        V v = vertex_ids.inverse().get(id);
+
+        if (v == null)
+        {
+        	if (vertex_factory != null)
+        		v = vertex_factory.get();
+        	else
+        		v = (V)id;
+            vertex_ids.put(v, id);
+            this.current_graph.addVertex(v);
+
+            // put remaining attribute/value pairs in vertex_data
+           	addExtraData(vertex_atts, vertex_metadata, v);
+        }
+        else
+            throw new SAXNotSupportedException("Node id \"" + id +
+            		" is a duplicate of an existing node ID");
+
+        this.current_vertex = v;
+    }
+
+
+    @SuppressWarnings("unchecked")
+	protected void createEdge(Attributes atts, TagState state)
+    	throws SAXNotSupportedException
+    {
+        Map<String,String> edge_atts = getAttributeMap(atts);
+
+        String id = edge_atts.remove("id");
+        E e;
+        if (edge_factory != null)
+        	e = edge_factory.get();
+        else
+            if (id != null)
+                e = (E)id;
+            else
+                throw new IllegalArgumentException("If no edge Supplier is supplied, " +
+                		"edge id may not be null: " + edge_atts);
+
+        if (id != null)
+        {
+            if (edge_ids.containsKey(e))
+                throw new SAXNotSupportedException("Edge id \"" + id +
+                		"\" is a duplicate of an existing edge ID");
+            edge_ids.put(e, id);
+        }
+
+        if (state == TagState.EDGE)
+        	assignEdgeSourceTarget(e, atts, edge_atts); //, id);
+
+        // put remaining attribute/value pairs in edge_data
+        addExtraData(edge_atts, edge_metadata, e);
+
+        this.current_edge = e;
+    }
+
+    protected void assignEdgeSourceTarget(E e, Attributes atts,
+    		Map<String, String> edge_atts)//, String id)
+    	throws SAXNotSupportedException
+    {
+        String source_id = edge_atts.remove("source");
+        if (source_id == null)
+            throw new SAXNotSupportedException("edge attribute list missing " +
+            		"'source': " + atts.toString());
+        V source = vertex_ids.inverse().get(source_id);
+        if (source == null)
+            throw new SAXNotSupportedException("specified 'source' attribute " +
+            		"\"" + source_id + "\" does not match any node ID");
+
+        String target_id = edge_atts.remove("target");
+        if (target_id == null)
+            throw new SAXNotSupportedException("edge attribute list missing " +
+            		"'target': " + atts.toString());
+        V target = vertex_ids.inverse().get(target_id);
+        if (target == null)
+            throw new SAXNotSupportedException("specified 'target' attribute " +
+            		"\"" + target_id + "\" does not match any node ID");
+
+        String directed = edge_atts.remove("directed");
+        EdgeType edge_type;
+        if (directed == null)
+            edge_type = default_edgetype;
+        else if (directed.equals("true"))
+            edge_type = EdgeType.DIRECTED;
+        else if (directed.equals("false"))
+            edge_type = EdgeType.UNDIRECTED;
+        else
+            throw new SAXNotSupportedException("Unrecognized edge direction specifier 'direction=\"" +
+            		directed + "\"': " + "source: " + source_id + ", target: " + target_id);
+
+        if (current_graph instanceof Graph)
+            ((Graph<V,E>)this.current_graph).addEdge(e, source, target,
+            		edge_type);
+        else
+            this.current_graph.addEdge(e, new Pair<V>(source, target));
+    }
+
+    /**
+     * @return a bidirectional map relating vertices and IDs.
+     */
+    public BiMap<V, String> getVertexIDs()
+    {
+        return vertex_ids;
+    }
+
+    /**
+     * Returns a bidirectional map relating edges and IDs.
+     * This is not guaranteed to always be populated (edge IDs are not
+     * required in GraphML files.
+     * @return a bidirectional map relating edges and IDs.
+     */
+    public BiMap<E, String> getEdgeIDs()
+    {
+        return edge_ids;
+    }
+
+    /**
+     * @return a map from graph type name to type metadata
+     */
+    public Map<String, GraphMLMetadata<G>> getGraphMetadata()
+    {
+    	return graph_metadata;
+    }
+
+    /**
+     * @return a map from vertex type name to type metadata
+     */
+    public Map<String, GraphMLMetadata<V>> getVertexMetadata()
+    {
+    	return vertex_metadata;
+    }
+
+    /**
+     * @return a map from edge type name to type metadata
+     */
+    public Map<String, GraphMLMetadata<E>> getEdgeMetadata()
+    {
+    	return edge_metadata;
+    }
+
+    /**
+     * @return a map from graphs to graph descriptions
+     */
+    public Map<G, String> getGraphDescriptions()
+    {
+        return graph_desc;
+    }
+
+    /**
+     * @return a map from vertices to vertex descriptions
+     */
+    public Map<V, String> getVertexDescriptions()
+    {
+        return vertex_desc;
+    }
+
+    /**
+     * @return a map from edges to edge descriptions
+     */
+    public Map<E, String> getEdgeDescriptions()
+    {
+        return edge_desc;
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/GraphMLWriter.java b/jung-io/src/main/java/edu/uci/ics/jung/io/GraphMLWriter.java
new file mode 100644
index 0000000..2e875a9
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/GraphMLWriter.java
@@ -0,0 +1,440 @@
+/*
+ * Created on June 16, 2008
+ *
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.io;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * Writes graphs out in GraphML format.
+ *
+ * Current known issues: 
+ * <ul>
+ * <li>Only supports one graph per output file.
+ * <li>Does not indent lines for text-format readability.
+ * </ul>
+ * 
+ */
+public class GraphMLWriter<V,E> 
+{
+    protected Function<? super V, String> vertex_ids;
+    protected Function<? super E, String> edge_ids;
+    protected Map<String, GraphMLMetadata<Hypergraph<V,E>>> graph_data;
+    protected Map<String, GraphMLMetadata<V>> vertex_data;
+    protected Map<String, GraphMLMetadata<E>> edge_data;
+    protected Function<? super V, String> vertex_desc;
+    protected Function<? super E, String> edge_desc;
+    protected Function<? super Hypergraph<V,E>, String> graph_desc;
+	protected boolean directed;
+	protected int nest_level;
+    
+	public GraphMLWriter() 
+	{
+	    vertex_ids = new Function<V,String>()
+	    { 
+	        public String apply(V v) 
+	        { 
+	            return v.toString(); 
+	        }
+	    };
+	    edge_ids = Functions.constant(null);
+	    graph_data = Collections.emptyMap();
+        vertex_data = Collections.emptyMap();
+        edge_data = Collections.emptyMap();
+        vertex_desc = Functions.constant(null);
+        edge_desc = Functions.constant(null);
+        graph_desc = Functions.constant(null);
+        nest_level = 0;
+	}
+	
+	
+	/**
+	 * Writes {@code graph} out using {@code w}.
+	 * @param graph the graph to write out
+	 * @param w the writer instance to which the graph data will be written out
+	 * @throws IOException if writing the graph fails
+	 */
+	public void save(Hypergraph<V,E> graph, Writer w) throws IOException
+	{
+		BufferedWriter bw = new BufferedWriter(w);
+
+		// write out boilerplate header
+		bw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+		bw.write("<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns/graphml\"\n" +
+				"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"  \n");
+		bw.write("xsi:schemaLocation=\"http://graphml.graphdrawing.org/xmlns/graphml\">\n");
+		
+		// write out data specifiers, including defaults
+		for (String key : graph_data.keySet())
+			writeKeySpecification(key, "graph", graph_data.get(key), bw);
+		for (String key : vertex_data.keySet())
+			writeKeySpecification(key, "node", vertex_data.get(key), bw);
+		for (String key : edge_data.keySet())
+			writeKeySpecification(key, "edge", edge_data.get(key), bw);
+
+		// write out graph-level information
+		// set edge default direction
+		bw.write("<graph edgedefault=\"");
+		directed = !(graph instanceof UndirectedGraph);
+        if (directed)
+            bw.write("directed\">\n");
+        else 
+            bw.write("undirected\">\n");
+
+        // write graph description, if any
+		String desc = graph_desc.apply(graph);
+		if (desc != null)
+			bw.write("<desc>" + desc + "</desc>\n");
+		
+		// write graph data out if any
+		for (String key : graph_data.keySet())
+		{
+			Function<Hypergraph<V,E>, ?> t = graph_data.get(key).transformer;
+			Object value = t.apply(graph);
+			if (value != null)
+				bw.write(format("data", "key", key, value.toString()) + "\n");
+		}
+        
+		// write vertex information
+        writeVertexData(graph, bw);
+		
+		// write edge information
+        writeEdgeData(graph, bw);
+
+        // close graph
+        bw.write("</graph>\n");
+        bw.write("</graphml>\n");
+        bw.flush();
+        
+        bw.close();
+	}
+
+//	public boolean save(Collection<Hypergraph<V,E>> graphs, Writer w)
+//	{
+//		return true;
+//	}
+
+	protected void writeIndentedText(BufferedWriter w, String to_write) throws IOException
+	{
+	    for (int i = 0; i < nest_level; i++)
+	        w.write("  ");
+	    w.write(to_write);
+	}
+	
+	protected void writeVertexData(Hypergraph<V,E> graph, BufferedWriter w) throws IOException
+	{
+		for (V v: graph.getVertices())
+		{
+			String v_string = String.format("<node id=\"%s\"", vertex_ids.apply(v));
+			boolean closed = false;
+			// write description out if any
+			String desc = vertex_desc.apply(v);
+			if (desc != null)
+			{
+				w.write(v_string + ">\n");
+				closed = true;
+				w.write("<desc>" + desc + "</desc>\n");
+			}
+			// write data out if any
+			for (String key : vertex_data.keySet())
+			{
+				Function<V, ?> t = vertex_data.get(key).transformer;
+				if (t != null)
+				{
+    				Object value = t.apply(v);
+    				if (value != null)
+    				{
+    					if (!closed)
+    					{
+    						w.write(v_string + ">\n");
+    						closed = true;
+    					}
+    					w.write(format("data", "key", key, value.toString()) + "\n");
+    				}
+				}
+			}
+			if (!closed)
+				w.write(v_string + "/>\n"); // no contents; close the node with "/>"
+			else
+			    w.write("</node>\n");
+		}
+	}
+
+	protected void writeEdgeData(Hypergraph<V,E> g, Writer w) throws IOException
+	{
+		for (E e: g.getEdges())
+		{
+			Collection<V> vertices = g.getIncidentVertices(e);
+			String id = edge_ids.apply(e);
+			String e_string;
+			boolean is_hyperedge = !(g instanceof Graph);
+            if (is_hyperedge)
+            {
+                e_string = "<hyperedge ";
+                // add ID if present
+                if (id != null)
+                    e_string += "id=\"" + id + "\" ";
+            }
+            else
+			{
+				Pair<V> endpoints = new Pair<V>(vertices);
+				V v1 = endpoints.getFirst();
+				V v2 = endpoints.getSecond();
+				e_string = "<edge ";
+				// add ID if present
+				if (id != null)
+					e_string += "id=\"" + id + "\" ";
+				// add edge type if doesn't match default
+				EdgeType edge_type = g.getEdgeType(e);
+				if (directed && edge_type == EdgeType.UNDIRECTED)
+					e_string += "directed=\"false\" ";
+				if (!directed && edge_type == EdgeType.DIRECTED)
+					e_string += "directed=\"true\" ";
+				e_string += "source=\"" + vertex_ids.apply(v1) + 
+					"\" target=\"" + vertex_ids.apply(v2) + "\"";
+			}
+			
+			boolean closed = false;
+			// write description out if any
+			String desc = edge_desc.apply(e);
+			if (desc != null)
+			{
+				w.write(e_string + ">\n");
+				closed = true;
+				w.write("<desc>" + desc + "</desc>\n");
+			}
+			// write data out if any
+			for (String key : edge_data.keySet())
+			{
+				Function<E, ?> t = edge_data.get(key).transformer;
+				Object value = t.apply(e);
+				if (value != null)
+				{
+					if (!closed)
+					{
+						w.write(e_string + ">\n");
+						closed = true;
+					}
+					w.write(format("data", "key", key, value.toString()) + "\n");
+				}
+			}
+			// if this is a hyperedge, write endpoints out if any
+			if (is_hyperedge)
+			{
+				for (V v : vertices)
+				{
+					if (!closed)
+					{
+						w.write(e_string + ">\n");
+						closed = true;
+					}
+					w.write("<endpoint node=\"" + vertex_ids.apply(v) + "\"/>\n");
+				}
+			}
+			
+			if (!closed)
+				w.write(e_string + "/>\n"); // no contents; close the edge with "/>"
+			else
+			    if (is_hyperedge)
+			        w.write("</hyperedge>\n");
+			    else
+			        w.write("</edge>\n");
+		}
+	}
+
+	protected void writeKeySpecification(String key, String type, 
+			GraphMLMetadata<?> ds, BufferedWriter bw) throws IOException
+	{
+		bw.write("<key id=\"" + key + "\" for=\"" + type + "\"");
+		boolean closed = false;
+		// write out description if any
+		String desc = ds.description;
+		if (desc != null)
+		{
+			if (!closed)
+			{
+				bw.write(">\n");
+				closed = true;
+			}
+			bw.write("<desc>" + desc + "</desc>\n");
+		}
+		// write out default if any
+		Object def = ds.default_value;
+		if (def != null)
+		{
+			if (!closed)
+			{
+				bw.write(">\n");
+				closed = true;
+			}
+			bw.write("<default>" + def.toString() + "</default>\n");
+		}
+		if (!closed)
+		    bw.write("/>\n");
+		else
+		    bw.write("</key>\n");
+	}
+	
+	protected String format(String type, String attr, String value, String contents)
+	{
+		return String.format("<%s %s=\"%s\">%s</%s>", 
+				type, attr, value, contents, type);
+	}
+	
+	/**
+	 * Provides an ID that will be used to identify a vertex in the output file.
+	 * If the vertex IDs are not set, the ID for each vertex will default to
+	 * the output of <code>toString</code> 
+	 * (and thus not guaranteed to be unique).
+	 * 
+	 * @param vertex_ids a mapping from vertex to ID
+	 */
+	public void setVertexIDs(Function<V, String> vertex_ids) 
+	{
+		this.vertex_ids = vertex_ids;
+	}
+
+
+	/**
+	 * Provides an ID that will be used to identify an edge in the output file.
+	 * If any edge ID is missing, no ID will be written out for the
+	 * corresponding edge.
+	 * 
+	 * @param edge_ids a mapping from edge to ID
+	 */
+	public void setEdgeIDs(Function<E, String> edge_ids) 
+	{
+		this.edge_ids = edge_ids;
+	}
+
+	/**
+	 * Provides a map from data type name to graph data.
+	 * 
+	 * @param graph_map map from data type name to graph data
+	 */
+	public void setGraphData(Map<String, GraphMLMetadata<Hypergraph<V,E>>> graph_map)
+	{
+		graph_data = graph_map;
+	}
+	
+    /**
+     * Provides a map from data type name to vertex data.
+     * 
+     * @param vertex_map map from data type name to vertex data
+     */
+	public void setVertexData(Map<String, GraphMLMetadata<V>> vertex_map)
+	{
+		vertex_data = vertex_map;
+	}
+	
+    /**
+     * Provides a map from data type name to edge data.
+     * 
+     * @param edge_map map from data type name to edge data
+     */
+	public void setEdgeData(Map<String, GraphMLMetadata<E>> edge_map)
+	{
+		edge_data = edge_map;
+	}
+	
+	/**
+	 * Adds a new graph data specification.
+	 * 
+	 * @param id the ID of the data to add
+	 * @param description a description of the data to add
+	 * @param default_value a default value for the data type
+	 * @param graph_transformer a mapping from graphs to their string representations
+	 */
+	public void addGraphData(String id, String description, String default_value,
+			Function<Hypergraph<V,E>, String> graph_transformer)
+	{
+		if (graph_data.equals(Collections.EMPTY_MAP))
+			graph_data = new HashMap<String, GraphMLMetadata<Hypergraph<V,E>>>();
+		graph_data.put(id, new GraphMLMetadata<Hypergraph<V,E>>(description, 
+				default_value, graph_transformer));
+	}
+	
+	/**
+	 * Adds a new vertex data specification.
+	 * 
+	 * @param id the ID of the data to add
+	 * @param description a description of the data to add
+	 * @param default_value a default value for the data type
+	 * @param vertex_transformer a mapping from vertices to their string representations
+	 */
+	public void addVertexData(String id, String description, String default_value,
+			Function<V, String> vertex_transformer)
+	{
+		if (vertex_data.equals(Collections.EMPTY_MAP))
+			vertex_data = new HashMap<String, GraphMLMetadata<V>>();
+		vertex_data.put(id, new GraphMLMetadata<V>(description, default_value, 
+				vertex_transformer));
+	}
+
+	/**
+	 * Adds a new edge data specification.
+	 * 
+	 * @param id the ID of the data to add
+	 * @param description a description of the data to add
+	 * @param default_value a default value for the data type
+	 * @param edge_transformer a mapping from edges to their string representations
+	 */
+	public void addEdgeData(String id, String description, String default_value,
+			Function<E, String> edge_transformer)
+	{
+		if (edge_data.equals(Collections.EMPTY_MAP))
+			edge_data = new HashMap<String, GraphMLMetadata<E>>();
+		edge_data.put(id, new GraphMLMetadata<E>(description, default_value, 
+				edge_transformer));
+	}
+
+	/**
+	 * Provides vertex descriptions.
+	 * @param vertex_desc a mapping from vertices to their descriptions
+	 */
+	public void setVertexDescriptions(Function<V, String> vertex_desc) 
+	{
+		this.vertex_desc = vertex_desc;
+	}
+
+    /**
+     * Provides edge descriptions.
+	 * @param edge_desc a mapping from edges to their descriptions
+     */
+	public void setEdgeDescriptions(Function<E, String> edge_desc) 
+	{
+		this.edge_desc = edge_desc;
+	}
+
+    /**
+     * Provides graph descriptions.
+	 * @param graph_desc a mapping from graphs to their descriptions
+     */
+	public void setGraphDescriptions(Function<Hypergraph<V,E>, String> graph_desc) 
+	{
+		this.graph_desc = graph_desc;
+	}
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/GraphReader.java b/jung-io/src/main/java/edu/uci/ics/jung/io/GraphReader.java
new file mode 100644
index 0000000..d6660ad
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/GraphReader.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+
+/**
+ * Interface for a reader of graph objects
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ * 
+ * @param <G>
+ *            the graph type
+ * @param <V> the vertex type
+ *            the vertex type
+ * @param <V> the edge type
+ *            the edge type
+ */
+public interface GraphReader<G extends Hypergraph<V, E>, V, E> {
+
+    /**
+     * Reads a single graph object, if one is available.
+     * 
+     * @return the next graph object, or null if none exists.
+     * @throws GraphIOException
+     *             thrown if an error occurred.
+     */
+    G readGraph() throws GraphIOException;
+
+    /**
+     * Closes this resource and frees any resources.
+     * 
+     * @throws GraphIOException
+     *             thrown if an error occurred.
+     */
+    void close() throws GraphIOException;
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/PajekNetReader.java b/jung-io/src/main/java/edu/uci/ics/jung/io/PajekNetReader.java
new file mode 100644
index 0000000..675956d
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/PajekNetReader.java
@@ -0,0 +1,532 @@
+/*
+ * Created on May 3, 2004
+ *
+ * Copyright (c) 2004, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.io;
+
+import java.awt.geom.Point2D;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.util.MapSettableTransformer;
+import edu.uci.ics.jung.algorithms.util.SettableTransformer;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+
+/**
+ * Reads a <code>Graph</code> from a Pajek NET formatted source.
+ * 
+ * <p>If the edge constraints specify that the graph is strictly undirected,
+ * and an "*Arcs" section is encountered, or if the edge constraints specify that the 
+ * graph is strictly directed, and an "*Edges" section is encountered,
+ * an <code>IllegalArgumentException</code> is thrown.
+ * 
+ * <p>If the edge constraints do not permit parallel edges, only the first encountered
+ * of a set of parallel edges will be read; subsequent edges in that set will be ignored.
+ * 
+ * <p>More restrictive edge constraints will cause vertices to be generated
+ * that are more time- and space-efficient.
+ * 
+ * At the moment, only supports the 
+ * part of the specification that defines: 
+ * <ul>
+ * <li> vertex ids (each must have a value from 1 to n, where n is the number of vertices)
+ * <li> vertex labels (must be in quotes if interrupted by whitespace)
+ * <li> directed edge connections (single or list)
+ * <li> undirected edge connections (single or list)
+ * <li> edge weights (not compatible with edges specified in list form)
+ * <br><b>note</b>: this version of PajekNetReader does not support multiple edge 
+ * weights, as PajekNetFile does; this behavior is consistent with the NET format. 
+ * <li> vertex locations (x and y; z coordinate is ignored)
+ * </ul> <p>
+ *
+ * Here is an example format for a directed graph without edge weights 
+ * and edges specified in list form: <br>
+ * <pre>
+ * *vertices [# of vertices] 
+ * 1 "a" 
+ * 2 "b" 
+ * 3 "c" 
+ * *arcslist 
+ * 1 2 3 
+ * 2 3  
+ * </pre>
+ *
+ * Here is an example format for an undirected graph with edge weights 
+ * and edges specified in non-list form: <br>
+ * <pre>
+ * *vertices [# of vertices] 
+ * 1 "a" 
+ * 2 "b" 
+ * 3 "c" 
+ * *edges 
+ * 1 2 0.1 
+ * 1 3 0.9 
+ * 2 3 1.0 
+ * </pre> 
+ * 
+ * @author Joshua O'Madadhain
+ * @see "'Pajek - Program for Analysis and Visualization of Large Networks', Vladimir Batagelj and Andrej Mrvar, http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/pajekman.pdf"
+ * @author Tom Nelson - converted to jung2
+ */
+public class PajekNetReader<G extends Graph<V,E>,V,E>
+{
+	protected Supplier<V> vertex_factory;
+	protected Supplier<E> edge_factory;
+
+    /**
+     * The map for vertex labels (if any) created by this class.
+     */
+	protected SettableTransformer<V, String> vertex_labels = new MapSettableTransformer<V,String>(new HashMap<V,String>());
+    
+    /**
+     * The map for vertex locations (if any) defined by this class.
+     */
+	protected SettableTransformer<V, Point2D> vertex_locations = new MapSettableTransformer<V,Point2D>(new HashMap<V,Point2D>());
+    
+	protected SettableTransformer<E, Number> edge_weights = 
+	    new MapSettableTransformer<E, Number>(new HashMap<E, Number>());
+	
+    /**
+     * Used to specify whether the most recently read line is a 
+     * Pajek-specific tag.
+     */
+    private static final Predicate<String> v_pred = new StartsWithPredicate("*vertices");
+    private static final Predicate<String> a_pred = new StartsWithPredicate("*arcs");
+    private static final Predicate<String> e_pred = new StartsWithPredicate("*edges");
+    private static final Predicate<String> t_pred = new StartsWithPredicate("*");
+    private static final Predicate<String> c_pred = Predicates.or(a_pred, e_pred);
+    protected static final Predicate<String> l_pred = ListTagPred.getInstance();
+    
+    /**
+     * Creates a PajekNetReader instance with the specified vertex and edge factories.
+     * @param vertex_factory the Supplier to use to create vertex objects
+     * @param edge_factory the Supplier to use to create edge objects
+     */
+    public PajekNetReader(Supplier<V> vertex_factory, Supplier<E> edge_factory) 
+    { 
+        this.vertex_factory = vertex_factory;
+        this.edge_factory = edge_factory;
+    }
+
+    /**
+     * Creates a PajekNetReader instance with the specified edge Supplier,
+     * and whose vertex objects correspond to the integer IDs assigned in the file.
+     * Note that this requires <code>V</code> to be assignment-compatible with
+     * an <code>Integer</code> value.
+     * @param edge_factory the Supplier to use to create edge objects
+     */
+    public PajekNetReader(Supplier<E> edge_factory)
+    {
+        this(null, edge_factory);
+    }
+    
+    /**
+     * Returns the graph created by parsing the specified file, as created
+     * by the specified Supplier.
+     * @param filename the file from which the graph is to be read
+     * @param graph_factory used to provide a graph instance
+     * @return a graph parsed from the specified file
+     * @throws IOException if the graph cannot be loaded
+     */
+    public G load(String filename, Supplier<? extends G> graph_factory) throws IOException
+    {
+        return load(new FileReader(filename), graph_factory.get());
+    }
+    
+    /**
+     * Returns the graph created by parsing the specified reader, as created
+     * by the specified Supplier.
+     * @param reader the reader instance from which the graph is to be read
+     * @param graph_factory used to provide a graph instance
+     * @return a graph parsed from the specified reader
+     * @throws IOException if the graph cannot be loaded
+     */
+    public G load(Reader reader, Supplier<? extends G> graph_factory) throws IOException
+    {
+        return load(reader, graph_factory.get());
+    }
+
+    /**
+     * Returns the graph created by parsing the specified file, by populating the
+     * specified graph.
+     * @param filename the file from which the graph is to be read
+     * @param g the graph instance to populate
+     * @return a graph parsed from the specified file
+     * @throws IOException if the graph cannot be loaded
+     */
+    public G load(String filename, G g) throws IOException
+    {
+        if (g == null)
+            throw new IllegalArgumentException("Graph provided must be non-null");
+        return load(new FileReader(filename), g);
+    }
+    
+    /**
+     * Populates the graph <code>g</code> with the graph represented by the
+     * Pajek-format data supplied by <code>reader</code>.  Stores edge weights,
+     * if any, according to <code>nev</code> (if non-null).
+     * 
+     * <p>Any existing vertices/edges of <code>g</code>, if any, are unaffected.
+     * 
+     * <p>The edge data are filtered according to <code>g</code>'s constraints, if any; thus, if 
+     * <code>g</code> only accepts directed edges, any undirected edges in the 
+     * input are ignored.
+     * 
+     * @param reader the reader from which the graph is to be read
+     * @param g the graph instance to populate
+     * @return a graph parsed from the specified reader
+     * @throws IOException if the graph cannot be loaded
+     */
+    public G load(Reader reader, G g) throws IOException
+    {
+        BufferedReader br = new BufferedReader(reader);
+                
+        // ignore everything until we see '*Vertices'
+        String curLine = skip(br, v_pred);
+        
+        if (curLine == null) // no vertices in the graph; return empty graph
+            return g;
+        
+        // create appropriate number of vertices
+        StringTokenizer st = new StringTokenizer(curLine);
+        st.nextToken(); // skip past "*vertices";
+        int num_vertices = Integer.parseInt(st.nextToken());
+        List<V> id = null;
+        if (vertex_factory != null)
+        {
+            for (int i = 1; i <= num_vertices; i++)
+                g.addVertex(vertex_factory.get());
+            id = new ArrayList<V>(g.getVertices());
+        }
+
+        // read vertices until we see any Pajek format tag ('*...')
+        curLine = null;
+        while (br.ready())
+        {
+            curLine = br.readLine();
+            if (curLine == null || t_pred.apply(curLine))
+                break;
+            if (curLine == "") // skip blank lines
+                continue;
+            
+            try
+            {
+                readVertex(curLine, id, num_vertices);
+            }
+            catch (IllegalArgumentException iae)
+            {
+                br.close();
+                reader.close();
+                throw iae;
+            }
+        }   
+
+        // skip over the intermediate stuff (if any) 
+        // and read the next arcs/edges section that we find
+        curLine = readArcsOrEdges(curLine, br, g, id, edge_factory);
+
+        // ditto
+        readArcsOrEdges(curLine, br, g, id, edge_factory);
+        
+        br.close();
+        reader.close();
+        
+        return g;
+    }
+
+    /**
+     * Parses <code>curLine</code> as a reference to a vertex, and optionally assigns 
+     * label and location information.
+     */
+    @SuppressWarnings("unchecked")
+    private void readVertex(String curLine, List<V> id, int num_vertices)
+    {
+        V v;
+        String[] parts = null;
+        int coord_idx = -1;     // index of first coordinate in parts; -1 indicates no coordinates found
+        String index;
+        String label = null;
+        // if there are quote marks on this line, split on them; label is surrounded by them
+        if (curLine.indexOf('"') != -1)
+        {
+            String[] initial_split = curLine.trim().split("\"");
+            // if there are any quote marks, there should be exactly 2
+            if (initial_split.length < 2 || initial_split.length > 3)
+                throw new IllegalArgumentException("Unbalanced (or too many) " +
+                		"quote marks in " + curLine);
+            index = initial_split[0].trim();
+            label = initial_split[1].trim();
+            if (initial_split.length == 3)
+                parts = initial_split[2].trim().split("\\s+", -1);
+            coord_idx = 0;
+        }
+        else // no quote marks, but are there coordinates?
+        {
+            parts = curLine.trim().split("\\s+", -1);
+            index = parts[0];
+            switch (parts.length)
+            {
+                case 1:         // just the ID; nothing to do, continue
+                    break;  
+                case 2:         // just the ID and a label
+                    label = parts[1];
+                    break;
+                case 3:         // ID, no label, coordinates
+                    coord_idx = 1;
+                    break;
+                default:         // ID, label, (x,y) coordinates, maybe some other stuff
+                    coord_idx = 2;
+                    break;
+            }
+        }
+        int v_id = Integer.parseInt(index) - 1; // go from 1-based to 0-based index
+        if (v_id >= num_vertices || v_id < 0)
+            throw new IllegalArgumentException("Vertex number " + v_id +
+                    "is not in the range [1," + num_vertices + "]");
+        if (id != null)
+          v = id.get(v_id);
+        else
+          v = (V)(new Integer(v_id));
+        // only attach the label if there's one to attach
+        if (label != null && label.length() > 0 && vertex_labels != null)
+        	vertex_labels.set(v, label);
+
+        // parse the rest of the line
+        if (coord_idx != -1 && parts != null && parts.length >= coord_idx+2 && vertex_locations != null)
+        {
+            double x = Double.parseDouble(parts[coord_idx]);
+            double y = Double.parseDouble(parts[coord_idx+1]);
+            vertex_locations.set(v, new Point2D.Double(x,y));
+        }
+    }
+
+    
+    
+    @SuppressWarnings("unchecked")
+    private String readArcsOrEdges(String curLine, BufferedReader br, Graph<V,E> g, List<V> id, Supplier<E> edge_factory)
+        throws IOException
+    {
+        String nextLine = curLine;
+        
+        // in case we're not there yet (i.e., format tag isn't arcs or edges)
+        if (! c_pred.apply(curLine))
+            nextLine = skip(br, c_pred);
+
+        boolean reading_arcs = false;
+        boolean reading_edges = false;
+        EdgeType directedness = null;
+        if (a_pred.apply(nextLine))
+        {
+            if (g instanceof UndirectedGraph) {
+                throw new IllegalArgumentException("Supplied undirected-only graph cannot be populated with directed edges");
+            } else {
+                reading_arcs = true;
+                directedness = EdgeType.DIRECTED;
+            }
+        }
+        if (e_pred.apply(nextLine))
+        {
+            if (g instanceof DirectedGraph)
+                throw new IllegalArgumentException("Supplied directed-only graph cannot be populated with undirected edges");
+            else
+                reading_edges = true;
+            directedness = EdgeType.UNDIRECTED;
+        }
+        
+        if (!(reading_arcs || reading_edges))
+            return nextLine;
+        
+        boolean is_list = l_pred.apply(nextLine);
+
+        while (br.ready())
+        {
+            nextLine = br.readLine();
+            if (nextLine == null || t_pred.apply(nextLine))
+                break;
+            if (curLine == "") // skip blank lines
+                continue;
+            
+            StringTokenizer st = new StringTokenizer(nextLine.trim());
+            
+            int vid1 = Integer.parseInt(st.nextToken()) - 1;
+            V v1;
+            if (id != null)
+              v1 = id.get(vid1);
+            else
+              v1 = (V)new Integer(vid1);
+
+            
+            if (is_list) // one source, multiple destinations
+            {
+                do
+                {
+                    createAddEdge(st, v1, directedness, g, id, edge_factory);
+                } while (st.hasMoreTokens());
+            }
+            else // one source, one destination, at most one weight
+            {
+                E e = createAddEdge(st, v1, directedness, g, id, edge_factory);
+                // get the edge weight if we care
+                if (edge_weights != null && st.hasMoreTokens())
+                    edge_weights.set(e, new Float(st.nextToken()));
+            }
+        }
+        return nextLine;
+    }
+
+    @SuppressWarnings("unchecked")
+    protected E createAddEdge(StringTokenizer st, V v1, 
+            EdgeType directed, Graph<V,E> g, List<V> id, Supplier<E> edge_factory)
+    {
+        int vid2 = Integer.parseInt(st.nextToken()) - 1;
+        V v2;
+        if (id != null)
+          v2 = id.get(vid2);
+        else
+          v2 = (V)new Integer(vid2);
+        E e = edge_factory.get();
+
+        // don't error-check this: let the graph implementation do whatever it's going to do 
+        // (add the edge, replace the existing edge, throw an exception--depends on the graph implementation)
+     	g.addEdge(e, v1, v2, directed);
+        return e;
+    }
+    
+    /**
+     * Returns the first line read from <code>br</code> for which <code>p</code> 
+     * returns <code>true</code>, or <code>null</code> if there is no
+     * such line.
+     * @param br the reader from which the graph is being read
+     * @param p predicate specifying what line to accept
+     * @return the first line from {@code br} that matches {@code p}, or null
+     * @throws IOException if an error is encountered while reading from {@code br}
+     */
+    protected String skip(BufferedReader br, Predicate<String> p) throws IOException
+    {
+        while (br.ready())
+        {
+            String curLine = br.readLine();
+            if (curLine == null)
+                break;
+            curLine = curLine.trim();
+            if (p.apply(curLine))
+                return curLine;
+        }
+        return null;
+    }
+    
+    /**
+     * A Predicate which evaluates to <code>true</code> if the
+     * argument starts with the constructor-specified String.
+     * 
+     * @author Joshua O'Madadhain
+     */
+    protected static class StartsWithPredicate implements Predicate<String> {
+        private String tag;
+        
+        protected StartsWithPredicate(String s) {
+            this.tag = s;
+        }
+        
+        public boolean apply(String str) {
+            return (str != null && str.toLowerCase().startsWith(tag));
+        }
+    }
+    
+    
+    /**
+     * A Predicate which evaluates to <code>true</code> if the
+     * argument ends with the string "list".
+     * 
+     * @author Joshua O'Madadhain
+     */
+    protected static class ListTagPred implements Predicate<String>
+    {
+        protected static ListTagPred instance;
+        
+        protected ListTagPred() {}
+        
+        protected static ListTagPred getInstance()
+        {
+            if (instance == null)
+                instance = new ListTagPred();
+            return instance;
+        }
+        
+        public boolean apply(String s)
+        {
+            return (s != null && s.toLowerCase().endsWith("list"));
+        }
+    }
+
+	/**
+	 * @return the vertexLocationTransformer
+	 */
+	public SettableTransformer<V, Point2D> getVertexLocationTransformer() {
+		return vertex_locations;
+	}
+
+	/**
+	 * Provides a Function which will be used to write out the vertex locations.
+	 * @param vertex_locations a container for the vertex locations
+	 */
+	public void setVertexLocationTransformer(SettableTransformer<V, Point2D> vertex_locations)
+	{
+	    this.vertex_locations = vertex_locations;
+	}
+	
+	/**
+	 * @return a mapping from vertices to their labels
+	 */
+	public SettableTransformer<V, String> getVertexLabeller() {
+		return vertex_labels;
+	}
+	
+	/**
+	 * Provides a Function which will be used to write out the vertex labels.
+	 * @param vertex_labels a container for the vertex labels
+	 */
+	public void setVertexLabeller(SettableTransformer<V, String> vertex_labels)
+	{
+	    this.vertex_labels = vertex_labels;
+	}
+    
+	/**
+	 * @return a mapping from edges to their weights
+	 */
+	public SettableTransformer<E, Number> getEdgeWeightTransformer() 
+	{
+	    return edge_weights;
+	}
+	
+	/**
+	 * Provides a Function which will be used to write out edge weights.
+	 * @param edge_weights a container for the edge weights
+	 */
+	public void setEdgeWeightTransformer(SettableTransformer<E, Number> edge_weights)
+	{
+	    this.edge_weights = edge_weights;
+	}
+
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/PajekNetWriter.java b/jung-io/src/main/java/edu/uci/ics/jung/io/PajekNetWriter.java
new file mode 100644
index 0000000..285e6c1
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/PajekNetWriter.java
@@ -0,0 +1,220 @@
+/*
+ * Created on May 4, 2004
+ *
+ * Copyright (c) 2004, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.io;
+
+import java.awt.geom.Point2D;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * Writes graphs in the Pajek NET format.
+ * 
+ * <p>Labels for vertices, edge weights, and vertex locations may each optionally
+ * be specified.  Note that vertex location coordinates 
+ * must be normalized to the interval [0, 1] on each axis in order to conform to the 
+ * Pajek specification.
+ * 
+ * @author Joshua O'Madadhain
+ * @author Tom Nelson - converted to jung2
+ */
+public class PajekNetWriter<V,E>
+{
+    /**
+     * Creates a new instance.
+     */
+    public PajekNetWriter()
+    {
+    }
+
+    /**
+     * Saves the graph to the specified file.
+     * @param g the graph to be saved
+     * @param filename the filename of the file to write the graph to
+     * @param vs mapping from vertices to labels
+     * @param nev mapping from edges to weights
+     * @param vld mapping from vertices to locations
+     * @throws IOException if the graph cannot be saved
+     */
+    public void save(Graph<V,E> g, String filename, Function<V,String> vs, 
+            Function<E,Number> nev, Function<V,Point2D> vld) throws IOException
+    {
+        save(g, new FileWriter(filename), vs, nev, vld);
+    }
+    
+    /**
+     * Saves the graph to the specified file.
+     * @param g the graph to be saved
+     * @param filename the filename of the file to write the graph to
+     * @param vs mapping from vertices to labels
+     * @param nev mapping from edges to weights
+     * @throws IOException if the graph cannot be saved
+     */
+    public void save(Graph<V,E> g, String filename, Function<V,String> vs, 
+            Function<E,Number> nev) throws IOException
+    {
+        save(g, new FileWriter(filename), vs, nev, null);
+    }
+    
+    /**
+     * Saves the graph to the specified file.  No vertex labels are written, and the 
+     * edge weights are written as 1.0.
+     * @param g the graph to be saved
+     * @param filename the filename of the file to write the graph to
+     * @throws IOException if the graph cannot be saved
+     */
+    public void save(Graph<V,E> g, String filename) throws IOException
+    {
+        save(g, filename, null, null, null);
+    }
+
+    /**
+     * Saves the graph to the specified writer.  No vertex labels are written, and the 
+     * edge weights are written as 1.0.
+     * @param g the graph to be saved
+     * @param w the writer instance to write the graph to
+     * @throws IOException if the graph cannot be saved
+     */
+    public void save(Graph<V,E> g, Writer w) throws IOException
+    {
+        save(g, w, null, null, null);
+    }
+
+    /**
+     * Saves the graph to the specified writer.
+     * @param g the graph to be saved
+     * @param w the writer instance to write the graph to
+     * @param vs mapping from vertices to labels
+     * @param nev mapping from edges to weights
+     * @throws IOException if the graph cannot be saved
+     */
+    public void save(Graph<V,E> g, Writer w, Function<V,String> vs, 
+            Function<E,Number> nev) throws IOException
+    {
+        save(g, w, vs, nev, null);
+    }
+    
+    /**
+     * Saves the graph to the specified writer.
+     * @param graph the graph to be saved
+     * @param w the writer instance to write the graph to
+     * @param vs mapping from vertices to labels (no labels are written if null)
+     * @param nev mapping from edges to weights (defaults to weights of 1.0 if null)
+     * @param vld mapping from vertices to locations (no locations are written if null)
+     * @throws IOException if the graph cannot be saved
+     */
+	public void save(Graph<V,E> graph, Writer w, Function<V,String> vs, 
+	        Function<E,Number> nev, Function<V,Point2D> vld) throws IOException
+    {
+        /*
+         * TODO: Changes we might want to make:
+         * - optionally writing out in list form
+         */
+        
+        BufferedWriter writer = new BufferedWriter(w);
+        if (nev == null)
+            nev = new Function<E, Number>() { public Number apply(E e) { return 1; } };
+        writer.write("*Vertices " + graph.getVertexCount());
+        writer.newLine();
+        
+        List<V> id = new ArrayList<V>(graph.getVertices());
+        for (V currentVertex : graph.getVertices())
+        {
+            // convert from 0-based to 1-based index
+            int v_id = id.indexOf(currentVertex) + 1;
+            writer.write(""+v_id); 
+            if (vs != null)
+            { 
+                String label = vs.apply(currentVertex);
+                if (label != null)
+                    writer.write (" \"" + label + "\"");
+            }
+            if (vld != null)
+            {
+                Point2D location = vld.apply(currentVertex);
+                if (location != null)
+                    writer.write (" " + location.getX() + " " + location.getY() + " 0.0");
+            }
+            writer.newLine();
+        }
+
+        Collection<E> d_set = new HashSet<E>();
+        Collection<E> u_set = new HashSet<E>();
+
+        boolean directed = graph instanceof DirectedGraph;
+
+        boolean undirected = graph instanceof UndirectedGraph;
+
+        // if it's strictly one or the other, no need to create extra sets
+        if (directed)
+            d_set.addAll(graph.getEdges());
+        if (undirected)
+            u_set.addAll(graph.getEdges());
+        if (!directed && !undirected) // mixed-mode graph
+        {
+        	u_set.addAll(graph.getEdges());
+        	d_set.addAll(graph.getEdges());
+        	for(E e : graph.getEdges()) {
+        		if(graph.getEdgeType(e) == EdgeType.UNDIRECTED) {
+        			d_set.remove(e);
+        		} else {
+        			u_set.remove(e);
+        		}
+        	}
+        }
+
+        // write out directed edges
+        if (!d_set.isEmpty())
+        {
+            writer.write("*Arcs");
+            writer.newLine();
+        }
+        for (E e : d_set)
+        {
+            int source_id = id.indexOf(graph.getEndpoints(e).getFirst()) + 1;
+            int target_id = id.indexOf(graph.getEndpoints(e).getSecond()) + 1;
+            float weight = nev.apply(e).floatValue();
+            writer.write(source_id + " " + target_id + " " + weight);
+            writer.newLine();
+        }
+
+        // write out undirected edges
+        if (!u_set.isEmpty())
+        {
+            writer.write("*Edges");
+            writer.newLine();
+        }
+        for (E e : u_set)
+        {
+            Pair<V> endpoints = graph.getEndpoints(e);
+            int v1_id = id.indexOf(endpoints.getFirst()) + 1;
+            int v2_id = id.indexOf(endpoints.getSecond()) + 1;
+            float weight = nev.apply(e).floatValue();
+            writer.write(v1_id + " " + v2_id + " " + weight);
+            writer.newLine();
+        }
+        writer.close();
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/AbstractMetadata.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/AbstractMetadata.java
new file mode 100644
index 0000000..e087c77
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/AbstractMetadata.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Abstract base class for metadata - implements the property functionality
+ * 
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public abstract class AbstractMetadata implements Metadata {
+
+    final private Map<String,String> properties = new HashMap<String, String>();
+    
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+    
+    public String getProperty(String key) {
+        return properties.get(key);
+    }
+    
+    public String setProperty(String key, String value) {
+        return properties.put(key, value);
+    }
+    
+    public void addData(DataMetadata data) {
+        properties.put(data.getKey(), data.getValue());
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/DataMetadata.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/DataMetadata.java
new file mode 100644
index 0000000..fede529
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/DataMetadata.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+/**
+ * Metadata structure for the 'data' GraphML element.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ * 
+ * @see "http://graphml.graphdrawing.org/specification.html"
+ */
+public class DataMetadata {
+
+    private String key;
+    private String value;
+    
+    public String getKey() {
+        return key;
+    }
+    
+    public void setKey(String key) {
+        this.key = key;
+    }
+    
+    public String getValue() {
+        return value;
+    }
+    
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/EdgeMetadata.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/EdgeMetadata.java
new file mode 100644
index 0000000..8034b06
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/EdgeMetadata.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+/**
+ * Metadata structure for the 'edge' GraphML element.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ *
+ * @see "http://graphml.graphdrawing.org/specification.html"
+ */
+public class EdgeMetadata extends AbstractMetadata {
+
+    private String id;
+    private Boolean directed;
+    private String source;
+    private String target;
+    private String sourcePort;
+    private String targetPort;
+    private String description;
+    private Object edge;
+    
+    public String getId() {
+        return id;
+    }
+
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+
+    public Boolean isDirected() {
+        return directed;
+    }
+
+
+    public void setDirected(Boolean directed) {
+        this.directed = directed;
+    }
+
+
+    public String getSource() {
+        return source;
+    }
+
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+
+    public String getTarget() {
+        return target;
+    }
+
+
+    public void setTarget(String target) {
+        this.target = target;
+    }
+
+
+    public String getSourcePort() {
+        return sourcePort;
+    }
+
+
+    public void setSourcePort(String sourcePort) {
+        this.sourcePort = sourcePort;
+    }
+
+
+    public String getTargetPort() {
+        return targetPort;
+    }
+
+
+    public void setTargetPort(String targetPort) {
+        this.targetPort = targetPort;
+    }
+
+
+    public String getDescription() {
+        return description;
+    }
+
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public Object getEdge() {
+        return edge;
+    }
+
+
+    public void setEdge(Object edge) {
+        this.edge = edge;
+    }
+
+
+    public MetadataType getMetadataType() {
+        return MetadataType.EDGE;
+    }
+
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/EndpointMetadata.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/EndpointMetadata.java
new file mode 100644
index 0000000..60a28da
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/EndpointMetadata.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+/**
+ * Metadata structure for the 'endpoint' GraphML element.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ *
+ * @see "http://graphml.graphdrawing.org/specification.html"
+ */
+public class EndpointMetadata extends AbstractMetadata {
+
+    public enum EndpointType {
+        IN,
+        OUT,
+        UNDIR
+    }
+    
+    private String id;
+    private String port;
+    private String node;
+    private String description;
+    private EndpointType endpointType = EndpointType.UNDIR;
+    
+    public String getId() {
+        return id;
+    }
+    public void setId(String id) {
+        this.id = id;
+    }
+    public String getPort() {
+        return port;
+    }
+    public void setPort(String port) {
+        this.port = port;
+    }
+    public String getNode() {
+        return node;
+    }
+    public void setNode(String node) {
+        this.node = node;
+    }
+    public EndpointType getEndpointType() {
+        return endpointType;
+    }
+    public void setEndpointType(EndpointType endpointType) {
+        this.endpointType = endpointType;
+    }        
+    public String getDescription() {
+        return description;
+    }
+    public void setDescription(String description) {
+        this.description = description;
+    }
+    public MetadataType getMetadataType() {
+        return MetadataType.ENDPOINT;
+    }
+    
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/ExceptionConverter.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/ExceptionConverter.java
new file mode 100644
index 0000000..9de9db1
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/ExceptionConverter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+import edu.uci.ics.jung.io.GraphIOException;
+
+import javax.xml.stream.XMLStreamException;
+
+/**
+ * Converts an exception to the a GraphIOException.  Runtime exceptions
+ * are checked for the cause.  If the cause is an XMLStreamException, it is
+ * converted to a GraphIOException.  Otherwise, the RuntimeException is
+ * rethrown.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class ExceptionConverter {
+
+    /**
+     * Converts an exception to the a GraphIOException.  Runtime exceptions
+     * are checked for the cause.  If the cause is an XMLStreamException, it is
+     * converted to a GraphReaderException.  Otherwise, the RuntimeException is
+     * rethrown.
+     *
+     * @param e the exception to be converted
+     * @throws GraphIOException the converted exception
+     */
+    static public void convert(Exception e) throws GraphIOException {
+
+        if (e instanceof GraphIOException) {
+            throw (GraphIOException) e;
+        }
+
+        if (e instanceof RuntimeException) {
+
+            // If the cause was an XMLStreamException, throw a GraphReaderException
+            if (e.getCause() instanceof XMLStreamException) {
+                throw new GraphIOException(e.getCause());
+            }
+
+            throw (RuntimeException) e;
+        }
+
+        throw new GraphIOException(e);
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMLConstants.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMLConstants.java
new file mode 100644
index 0000000..8567b6e
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMLConstants.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+/**
+ * Provides some constants for element/attribute names in GraphML
+ * 
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class GraphMLConstants {
+
+    public static final String GRAPHML_NAME = "graphml";
+    public static final String GRAPH_NAME = "graph";
+    public static final String NODE_NAME = "node";
+    public static final String EDGE_NAME = "edge";
+    public static final String ENDPOINT_NAME = "endpoint";
+    public static final String HYPEREDGE_NAME = "hyperedge";
+    public static final String PORT_NAME = "port";
+    public static final String KEY_NAME = "key";
+    public static final String DATA_NAME = "data";
+    public static final String ALL_NAME = "all";
+    public static final String ID_NAME = "id";
+    public static final String FOR_NAME = "for";
+    public static final String DESC_NAME = "desc";
+    public static final String DEFAULT_NAME = "default";
+    public static final String ATTRNAME_NAME = "attr.name";
+    public static final String ATTRTYPE_NAME = "attr.type";
+    public static final String NAME_NAME = "name";
+    public static final String EDGEDEFAULT_NAME = "edgedefault";
+    public static final String TYPE_NAME = "type";
+    public static final String IN_NAME = "in";
+    public static final String OUT_NAME = "out";
+    public static final String UNDIR_NAME = "undir";
+    public static final String DIRECTED_NAME = "directed";
+    public static final String UNDIRECTED_NAME = "undirected";
+    public static final String SOURCE_NAME = "source";
+    public static final String TARGET_NAME = "target";
+    public static final String SOURCEPORT_NAME = "sourceport";
+    public static final String TARGETPORT_NAME = "targetport";
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMLDocument.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMLDocument.java
new file mode 100644
index 0000000..e2ac153
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMLDocument.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Maintains all the metadata read in from a single GraphML XML document.
+ */
+public class GraphMLDocument {
+
+    final private KeyMap keyMap = new KeyMap();
+    final private List<GraphMetadata> graphMetadata = new ArrayList<GraphMetadata>();
+
+    public KeyMap getKeyMap() {
+        return keyMap;
+    }
+
+    public List<GraphMetadata> getGraphMetadata() {
+        return graphMetadata;
+    }
+
+    public void clear() {
+        graphMetadata.clear();
+        keyMap.clear();
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMLReader2.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMLReader2.java
new file mode 100644
index 0000000..9df78a2
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMLReader2.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.InputStream;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.GraphReader;
+import edu.uci.ics.jung.io.graphml.parser.ElementParserRegistry;
+import edu.uci.ics.jung.io.graphml.parser.GraphMLEventFilter;
+
+/**
+ * Reads in data from a GraphML-formatted file and generates graphs based on
+ * that data. Does not currently support nested graphs.
+ * 
+ * <p>Note that the user is responsible for supplying a graph
+ * <code>Transformer</code> that will create graphs capable of supporting the
+ * edge types in the supplied GraphML file. If the graph generated by the
+ * <code>Factory</code> is not compatible (for example: if the graph does not
+ * accept directed edges, and the GraphML file contains a directed edge) then
+ * the results are graph-implementation-dependent.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ * 
+ * @param <G>
+ * The graph type to be read from the GraphML file
+ * @param <V> the vertex type
+ * The vertex type used by the graph
+ * @param <V> the edge type
+ * The edge type used by the graph
+ * @see "http://graphml.graphdrawing.org/specification.html"
+ */
+public class GraphMLReader2<G extends Hypergraph<V, E>, V, E> implements
+        GraphReader<G, V, E> {
+
+    protected XMLEventReader xmlEventReader;
+    protected Reader fileReader;
+    protected Function<GraphMetadata, G> graphTransformer;
+    protected Function<NodeMetadata, V> vertexTransformer;
+    protected Function<EdgeMetadata, E> edgeTransformer;
+    protected Function<HyperEdgeMetadata, E> hyperEdgeTransformer;
+    protected boolean initialized;
+    final protected GraphMLDocument document = new GraphMLDocument();
+    final protected ElementParserRegistry<G,V,E> parserRegistry;
+    private InputStream inputStream ;
+
+    /**
+     * Constructs a GraphML reader around the given reader. This constructor
+     * requires the user to supply transformation functions to convert from the
+     * GraphML metadata to Graph, Vertex, Edge instances. These Function
+     * functions can be used as purely factories (i.e. the metadata is
+     * disregarded) or can use the metadata to set particular fields in the
+     * objects.
+     *
+     * @param fileReader           the reader for the input GraphML document.
+     * @param graphTransformer     Transformation function to convert from GraphML GraphMetadata
+     *                             to graph objects. This must be non-null.
+     * @param vertexTransformer    Transformation function to convert from GraphML NodeMetadata
+     *                             to vertex objects. This must be non-null.
+     * @param edgeTransformer      Transformation function to convert from GraphML EdgeMetadata
+     *                             to edge objects. This must be non-null.
+     * @param hyperEdgeTransformer Transformation function to convert from GraphML
+     *                             HyperEdgeMetadata to edge objects. This must be non-null.
+     * @throws IllegalArgumentException thrown if any of the arguments are null.
+     */
+    public GraphMLReader2(Reader fileReader,
+                          Function<GraphMetadata, G> graphTransformer,
+                          Function<NodeMetadata, V> vertexTransformer,
+                          Function<EdgeMetadata, E> edgeTransformer,
+                          Function<HyperEdgeMetadata, E> hyperEdgeTransformer) {
+
+        if (fileReader == null) {
+            throw new IllegalArgumentException(
+                    "Argument fileReader must be non-null");
+        }        
+        
+        if (graphTransformer == null) {
+            throw new IllegalArgumentException(
+            "Argument graphTransformer must be non-null");
+        }        
+
+        if (vertexTransformer == null) {
+            throw new IllegalArgumentException(
+                    "Argument vertexTransformer must be non-null");
+        }        
+        
+        if (edgeTransformer == null) {
+            throw new IllegalArgumentException(
+                    "Argument edgeTransformer must be non-null");
+        }        
+        
+        if (hyperEdgeTransformer == null) {
+            throw new IllegalArgumentException(
+                    "Argument hyperEdgeTransformer must be non-null");
+        }
+        
+        this.fileReader = fileReader;
+        this.graphTransformer = graphTransformer;
+        this.vertexTransformer = vertexTransformer;
+        this.edgeTransformer = edgeTransformer;
+        this.hyperEdgeTransformer = hyperEdgeTransformer;
+        
+        // Create the parser registry.
+        this.parserRegistry = new ElementParserRegistry<G,V,E>(document.getKeyMap(), 
+                graphTransformer, vertexTransformer, edgeTransformer, hyperEdgeTransformer);
+    }
+
+    /**
+     * Constructs a GraphML reader around the given reader. This constructor
+     * requires the user to supply transformation functions to convert from the
+     * GraphML metadata to Graph, Vertex, Edge instances. These Function
+     * functions can be used as purely factories (i.e. the metadata is
+     * disregarded) or can use the metadata to set particular fields in the
+     * objects.
+     *
+     * @param inputStream          the inputstream for the input GraphML document.
+     * @param graphTransformer     Transformation function to convert from GraphML GraphMetadata
+     *                             to graph objects. This must be non-null.
+     * @param vertexTransformer    Transformation function to convert from GraphML NodeMetadata
+     *                             to vertex objects. This must be non-null.
+     * @param edgeTransformer      Transformation function to convert from GraphML EdgeMetadata
+     *                             to edge objects. This must be non-null.
+     * @param hyperEdgeTransformer Transformation function to convert from GraphML
+     *                             HyperEdgeMetadata to edge objects. This must be non-null.
+     * @throws IllegalArgumentException thrown if any of the arguments are null.
+     */
+    public GraphMLReader2(InputStream inputStream,
+                          Function<GraphMetadata, G> graphTransformer,
+                          Function<NodeMetadata, V> vertexTransformer,
+                          Function<EdgeMetadata, E> edgeTransformer,
+                          Function<HyperEdgeMetadata, E> hyperEdgeTransformer) {
+
+        if (inputStream == null) {
+            throw new IllegalArgumentException(
+                    "Argument inputStream must be non-null");
+        }        
+        
+        if (graphTransformer == null) {
+            throw new IllegalArgumentException(
+            "Argument graphTransformer must be non-null");
+        }        
+
+        if (vertexTransformer == null) {
+            throw new IllegalArgumentException(
+                    "Argument vertexTransformer must be non-null");
+        }        
+        
+        if (edgeTransformer == null) {
+            throw new IllegalArgumentException(
+                    "Argument edgeTransformer must be non-null");
+        }        
+        
+        if (hyperEdgeTransformer == null) {
+            throw new IllegalArgumentException(
+                    "Argument hyperEdgeTransformer must be non-null");
+        }
+        
+        this.inputStream = inputStream;
+        this.graphTransformer = graphTransformer;
+        this.vertexTransformer = vertexTransformer;
+        this.edgeTransformer = edgeTransformer;
+        this.hyperEdgeTransformer = hyperEdgeTransformer;
+        
+        // Create the parser registry.
+        this.parserRegistry = new ElementParserRegistry<G,V,E>(document.getKeyMap(), 
+                graphTransformer, vertexTransformer, edgeTransformer, hyperEdgeTransformer);
+    }
+
+    /**
+     * Gets the current Function that is being used for graph objects.
+     *
+     * @return the current Function.
+     */
+    public Function<GraphMetadata, G> getGraphTransformer() {
+        return graphTransformer;
+    }
+
+    /**
+     * Gets the current Function that is being used for vertex objects.
+     *
+     * @return the current Function.
+     */
+    public Function<NodeMetadata, V> getVertexTransformer() {
+        return vertexTransformer;
+    }
+
+    /**
+     * Gets the current Function that is being used for edge objects.
+     *
+     * @return the current Function.
+     */
+    public Function<EdgeMetadata, E> getEdgeTransformer() {
+        return edgeTransformer;
+    }
+
+    /**
+     * Gets the current Function that is being used for hyperedge objects.
+     *
+     * @return the current Function.
+     */
+    public Function<HyperEdgeMetadata, E> getHyperEdgeTransformer() {
+        return hyperEdgeTransformer;
+    }
+
+    /**
+     * Verifies the object state and initializes this reader. All Function
+     * properties must be set and be non-null or a <code>GraphReaderException
+     * </code> will be thrown. This method may be called more than once.
+     * Successive calls will have no effect.
+     *
+     * @throws edu.uci.ics.jung.io.GraphIOException thrown if an error occurred.
+     */
+    public void init() throws GraphIOException {
+
+        try {
+
+            if (!initialized) {
+
+                // Create the event reader.
+                XMLInputFactory Supplier = XMLInputFactory.newInstance();
+                if(fileReader==null && inputStream != null) {
+                    xmlEventReader = Supplier.createXMLEventReader(inputStream);
+                } else {
+                    xmlEventReader = Supplier.createXMLEventReader(fileReader);
+                }
+                xmlEventReader = Supplier.createFilteredReader(xmlEventReader,
+                        new GraphMLEventFilter());
+
+                initialized = true;
+            }
+
+        } catch( Exception e ) {
+            ExceptionConverter.convert(e);
+        }
+    }
+
+    /**
+     * Closes the GraphML reader and disposes of any resources.
+     *
+     * @throws edu.uci.ics.jung.io.GraphIOException thrown if an error occurs.
+     */
+    public void close() throws GraphIOException {
+        try {
+
+            // Clear the contents of the document.
+            document.clear();
+
+            if (xmlEventReader != null) {
+                xmlEventReader.close();
+            }
+
+            if (fileReader != null) {
+                fileReader.close();
+            }
+            
+            if (inputStream != null) {
+                inputStream.close();
+            }
+
+        } catch (IOException e) {
+            throw new GraphIOException(e);
+        } catch (XMLStreamException e) {
+            throw new GraphIOException(e);
+        } finally {
+            fileReader = null;
+            inputStream = null;
+            xmlEventReader = null;
+            graphTransformer = null;
+            vertexTransformer = null;
+            edgeTransformer = null;
+            hyperEdgeTransformer = null;
+        }
+    }
+
+    /**
+     * Returns the object that contains the metadata read in from the GraphML
+     * document
+     *
+     * @return the GraphML document
+     */
+    public GraphMLDocument getGraphMLDocument() {
+        return document;
+    }
+
+    /**
+     * Reads a single graph object from the GraphML document. Automatically
+     * calls <code>init</code> to initialize the state of the reader.
+     *
+     * @return the graph that was read if one was found, otherwise null.
+     */
+    @SuppressWarnings("unchecked")
+    public G readGraph() throws GraphIOException {
+
+        try {
+
+            // Initialize if not already.
+            init();
+
+            while (xmlEventReader.hasNext()) {
+
+                XMLEvent event = xmlEventReader.nextEvent();
+                if (event.isStartElement()) {
+                    StartElement element = (StartElement) event;
+                    String name = element.getName().getLocalPart();
+
+                    // The element should be one of: key, graph, graphml
+                    if (GraphMLConstants.KEY_NAME.equals(name)) {
+
+                        // Parse the key object.
+                        Key key = (Key) parserRegistry.getParser(name).parse(
+                                xmlEventReader, element);
+
+                        // Add the key to the key map.
+                        document.getKeyMap().addKey(key);
+
+                    } else if (GraphMLConstants.GRAPH_NAME.equals(name)) {
+
+                        // Parse the graph.
+                        GraphMetadata graph = (GraphMetadata) parserRegistry
+                                .getParser(name).parse(xmlEventReader, element);
+
+                        // Add it to the graph metadata list.
+                        document.getGraphMetadata().add(graph);
+                        
+                        // Return the graph object.
+                        return (G)graph.getGraph();
+
+                    } else if (GraphMLConstants.GRAPHML_NAME.equals(name)) {
+                        // Ignore the graphML object.
+                    } else {
+
+                        // Encounted an unknown element - just skip by it.
+                        parserRegistry.getUnknownElementParser().parse(
+                                xmlEventReader, element);
+                    }
+
+                } else if (event.isEndDocument()) {
+                    break;
+                }
+            }
+
+        } catch (Exception e) {
+            ExceptionConverter.convert(e);
+        }
+
+        // We didn't read anything from the document.
+        throw new GraphIOException("Unable to read Graph from document - the document could be empty");
+    }    
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMetadata.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMetadata.java
new file mode 100644
index 0000000..8ef0a8f
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/GraphMetadata.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Metadata structure for the 'graph' GraphML element.
+ * 
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ * 
+ * @see "http://graphml.graphdrawing.org/specification.html"
+ */
+public class GraphMetadata extends AbstractMetadata {
+
+    public enum EdgeDefault {
+        DIRECTED, UNDIRECTED
+    }
+
+    private String id;
+    private EdgeDefault edgeDefault;
+    private String description;
+    private Object graph;
+    final private Map<Object, NodeMetadata> nodes = new HashMap<Object, NodeMetadata>();
+    final private Map<Object, EdgeMetadata> edges = new HashMap<Object, EdgeMetadata>();
+    final private Map<Object, HyperEdgeMetadata> hyperEdges = new HashMap<Object, HyperEdgeMetadata>();
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public EdgeDefault getEdgeDefault() {
+        return edgeDefault;
+    }
+
+    public void setEdgeDefault(EdgeDefault edgeDefault) {
+        this.edgeDefault = edgeDefault;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String desc) {
+        this.description = desc;
+    }
+
+    public void addNodeMetadata(Object vertex, NodeMetadata metadata) {
+        nodes.put(vertex, metadata);
+    }
+
+    public NodeMetadata getNodeMetadata(Object vertex) {
+        return nodes.get(vertex);
+    }
+
+    public Map<Object, NodeMetadata> getNodeMap() {
+        return nodes;
+    }
+
+    public void addEdgeMetadata(Object edge, EdgeMetadata metadata) {
+        edges.put(edge, metadata);
+    }
+
+    public EdgeMetadata getEdgeMetadata(Object edge) {
+        return edges.get(edge);
+    }
+
+    public Map<Object, EdgeMetadata> getEdgeMap() {
+        return edges;
+    }
+
+    public void addHyperEdgeMetadata(Object edge, HyperEdgeMetadata metadata) {
+        hyperEdges.put(edge, metadata);
+    }
+
+    public HyperEdgeMetadata getHyperEdgeMetadata(Object edge) {
+        return hyperEdges.get(edge);
+    }
+
+    public Map<Object, HyperEdgeMetadata> getHyperEdgeMap() {
+        return hyperEdges;
+    }
+
+    public Object getGraph() {
+        return graph;
+    }
+
+    public void setGraph(Object graph) {
+        this.graph = graph;
+    }
+
+    public MetadataType getMetadataType() {
+        return MetadataType.GRAPH;
+    }
+
+    /**
+     * Gets the property for the given vertex object.
+     * 
+     * @param vertex
+     *            the subject vertex
+     * @param key
+     *            the property key
+     * @return the property value
+     * @throws IllegalArgumentException
+     *             thrown if there is no metadata associated with the provided
+     *             vertex object.
+     */
+    public String getVertexProperty(Object vertex, String key)
+            throws IllegalArgumentException {
+        NodeMetadata metadata = getNodeMetadata(vertex);
+        if (metadata == null) {
+            throw new IllegalArgumentException(
+                    "Metadata does not exist for provided vertex");
+        }
+
+        return metadata.getProperty(key);
+    }
+
+    /**
+     * Gets the property for the given edge object.
+     * 
+     * @param edge
+     *            the subject edge.
+     * @param key
+     *            the property key
+     * @return the property value
+     * @throws IllegalArgumentException
+     *             thrown if there is no metadata associated with the provided
+     *             edge object.
+     */
+    public String getEdgeProperty(Object edge, String key)
+            throws IllegalArgumentException {
+
+        // First, try standard edges.
+        EdgeMetadata em = getEdgeMetadata(edge);
+        if (em != null) {
+            return em.getProperty(key);
+        }
+
+        // Next, try hyperedges.
+        HyperEdgeMetadata hem = getHyperEdgeMetadata(edge);
+        if (hem != null) {
+            return hem.getProperty(key);
+        }
+
+        // Couldn't find the edge.
+        throw new IllegalArgumentException(
+                "Metadata does not exist for provided edge");
+    }
+
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/HyperEdgeMetadata.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/HyperEdgeMetadata.java
new file mode 100644
index 0000000..e0db527
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/HyperEdgeMetadata.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Metadata structure for the 'hyperedge' GraphML element.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ *
+ * @see "http://graphml.graphdrawing.org/specification.html"
+ */
+public class HyperEdgeMetadata extends AbstractMetadata {
+
+    private String id;
+    private String description;
+    private Object edge;
+    final private List<EndpointMetadata> endpoints = new ArrayList<EndpointMetadata>();
+    
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public void addEndpoint( EndpointMetadata endpoint ) {
+        endpoints.add(endpoint);
+    }
+    
+    public List<EndpointMetadata> getEndpoints() {
+        return endpoints;
+    }
+
+    public Object getEdge() {
+        return edge;
+    }
+
+    public void setEdge(Object edge) {
+        this.edge = edge;
+    }
+
+    public MetadataType getMetadataType() {
+        return MetadataType.HYPEREDGE;
+    }
+    
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/Key.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/Key.java
new file mode 100644
index 0000000..fbd7ae0
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/Key.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+import java.util.Map;
+
+/**
+ * GraphML key object that was parsed from the input stream.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class Key {
+
+    /**
+     * Enumeration for the 'for' type of this key.  The for property indicates 
+     * which elements (e.g. graph, node, edge) this key applies to.
+     */
+    public enum ForType {
+        ALL, GRAPH, NODE, EDGE, HYPEREDGE, PORT, ENDPOINT
+    }
+    
+    private String id;
+    private String description;
+    private String attributeName;
+    private String attributeType;
+    private String defaultValue;
+    private ForType forType = ForType.ALL;
+    
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getAttributeName() {
+        return attributeName;
+    }
+
+    public void setAttributeName(String attributeName) {
+        this.attributeName = attributeName;
+    }
+
+    public String getAttributeType() {
+        return attributeType;
+    }
+
+    public void setAttributeType(String attributeType) {
+        this.attributeType = attributeType;
+    }
+
+    public String getDefaultValue() {
+        return defaultValue;
+    }
+
+    public void setDefaultValue(String defaultValue) {
+        this.defaultValue = defaultValue;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public void setForType(ForType forType) {
+        this.forType = forType;
+    }
+
+    public String getId() {
+        return this.id;
+    }
+    
+    public String defaultValue() {
+        return this.defaultValue;
+    }
+    
+    public ForType getForType() {
+        return this.forType;
+    }
+
+    public void applyKey( Metadata metadata ) {
+        Map<String,String> props = metadata.getProperties();                        
+        if( defaultValue != null && !props.containsKey(id) ) {
+            props.put(id, defaultValue);
+        }
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/KeyMap.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/KeyMap.java
new file mode 100644
index 0000000..2739fa6
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/KeyMap.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A KeyMap is a storage mechanism for the keys read from the GraphML file. It
+ * stores the keys indexed by the type of GraphML metadata (node, edge, etc)
+ * that the key applies to. The <code>applyKeys</code> method will obtain the
+ * list of keys that apply to the given metadata type and apply the keys
+ * one-by-one to the metadata.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class KeyMap {
+
+    final private Map<Metadata.MetadataType, List<Key>> map = new HashMap<Metadata.MetadataType, List<Key>>();
+
+    /**
+     * Adds the given key to the map.
+     *
+     * @param key the key to be added.
+     */
+    public void addKey(Key key) {
+
+        switch (key.getForType()) {
+            case EDGE: {
+                getKeyList(Metadata.MetadataType.EDGE).add(key);
+                break;
+            }
+            case ENDPOINT: {
+                getKeyList(Metadata.MetadataType.ENDPOINT).add(key);
+                break;
+            }
+            case GRAPH: {
+                getKeyList(Metadata.MetadataType.GRAPH).add(key);
+                break;
+            }
+            case HYPEREDGE: {
+                getKeyList(Metadata.MetadataType.HYPEREDGE).add(key);
+                break;
+            }
+            case NODE: {
+                getKeyList(Metadata.MetadataType.NODE).add(key);
+                break;
+            }
+            case PORT: {
+                getKeyList(Metadata.MetadataType.PORT).add(key);
+                break;
+            }
+            default: {
+
+                // Default = ALL
+                getKeyList(Metadata.MetadataType.EDGE).add(key);
+                getKeyList(Metadata.MetadataType.ENDPOINT).add(key);
+                getKeyList(Metadata.MetadataType.GRAPH).add(key);
+                getKeyList(Metadata.MetadataType.HYPEREDGE).add(key);
+                getKeyList(Metadata.MetadataType.NODE).add(key);
+                getKeyList(Metadata.MetadataType.PORT).add(key);
+            }
+        }
+    }
+
+    /**
+     * Applies all keys that are applicable to the given metadata.
+     *
+     * @param metadata the target metadata.
+     */
+    public void applyKeys(Metadata metadata) {
+
+        List<Key> keys = getKeyList(metadata.getMetadataType());
+        for (Key key : keys) {
+            key.applyKey(metadata);
+        }
+    }
+
+    /**
+     * Clears this map.
+     */
+    public void clear() {
+        map.clear();
+    }
+
+    /**
+     * Retrieves the set of entries contained in this map.
+     *
+     * @return all of the entries in this map.
+     */
+    public Set<Map.Entry<Metadata.MetadataType, List<Key>>> entrySet() {
+        return map.entrySet();
+    }
+
+    /**
+     * Gets the list for the given metadata type. If doesn't exist, the list is
+     * created.
+     *
+     * @param type the metadata type.
+     * @return the list for the metadata type.
+     */
+    private List<Key> getKeyList(Metadata.MetadataType type) {
+
+        List<Key> keys = map.get(type);
+        if (keys == null) {
+            keys = new ArrayList<Key>();
+            map.put(type, keys);
+        }
+
+        return keys;
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/Metadata.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/Metadata.java
new file mode 100644
index 0000000..bb8faa1
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/Metadata.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+import java.util.Map;
+
+/**
+ * Interface for any GraphML metadata.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public interface Metadata {
+
+    /**
+     * Metadata type enumeration
+     */
+    enum MetadataType {
+        GRAPH, NODE, EDGE, HYPEREDGE, PORT, ENDPOINT
+    }
+
+    /**
+     * Gets the metadata type of this object.
+     * 
+     * @return the metadata type
+     */
+    MetadataType getMetadataType();
+
+    /**
+     * Gets any properties that were associated with this metadata in the
+     * GraphML
+     * 
+     * @return GraphML properties
+     */
+    Map<String, String> getProperties();
+}
\ No newline at end of file
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/NodeMetadata.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/NodeMetadata.java
new file mode 100644
index 0000000..69be391
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/NodeMetadata.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Metadata structure for the 'node' GraphML element.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ * 
+ * @see "http://graphml.graphdrawing.org/specification.html"
+ */
+public class NodeMetadata extends AbstractMetadata {
+
+    private String id;
+    private String description;
+    private Object vertex;
+    final private List<PortMetadata> ports = new ArrayList<PortMetadata>();
+    
+    public String getId() {
+        return id;
+    }
+    
+    public void setId(String id) {
+        this.id = id;
+    }
+    
+    public String getDescription() {
+        return description;
+    }
+    
+    public void setDescription(String desc) {
+        this.description = desc;
+    }
+    
+    public void addPort(PortMetadata port) {
+        ports.add(port);
+    }
+    
+    public List<PortMetadata> getPorts() {
+        return ports;
+    }
+    
+    public Object getVertex() {
+        return vertex;
+    }
+
+    public void setVertex(Object vertex) {
+        this.vertex = vertex;
+    }
+
+    public MetadataType getMetadataType() {
+        return MetadataType.NODE;
+    }
+
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/PortMetadata.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/PortMetadata.java
new file mode 100644
index 0000000..b85e298
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/PortMetadata.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+/**
+ * Metadata structure for the 'port' GraphML element.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ * 
+ * @see "http://graphml.graphdrawing.org/specification.html"
+ */
+public class PortMetadata extends AbstractMetadata {
+
+    private String name;
+    private String description;
+    
+    public String getName() {
+        return name;
+    }
+    
+    public void setName(String name) {
+        this.name = name;
+    }
+    
+    public String getDescription() {
+        return description;
+    }
+    
+    public void setDescription(String desc) {
+        this.description = desc;
+    }
+    
+    public MetadataType getMetadataType() {
+        return MetadataType.PORT;
+    }
+
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/AbstractElementParser.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/AbstractElementParser.java
new file mode 100644
index 0000000..db9aff7
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/AbstractElementParser.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.Metadata;
+
+/**
+ * Base class for element parsers - provides some minimal functionality.
+ * 
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public abstract class AbstractElementParser<G extends Hypergraph<V,E>,V,E> implements ElementParser {
+
+    final private ParserContext<G,V,E> parserContext;
+    protected AbstractElementParser(ParserContext<G,V,E> parserContext) {
+        this.parserContext = parserContext;
+    }
+    
+    public ParserContext<G,V,E> getParserContext() {
+        return this.parserContext;
+    }
+    
+    public ElementParser getParser(String localName) {
+        return parserContext.getElementParserRegistry().getParser(localName);
+    }
+    
+    public void applyKeys(Metadata metadata) {
+        getParserContext().getKeyMap().applyKeys(metadata);
+    }
+    
+    public ElementParser getUnknownParser() {
+        return parserContext.getElementParserRegistry().getUnknownElementParser();
+    }
+    
+    protected void verifyMatch(StartElement start, EndElement end)
+            throws GraphIOException {
+
+        String startName = start.getName().getLocalPart();
+        String endName = end.getName().getLocalPart();
+        if (!startName.equals(endName)) {
+            throw new GraphIOException(
+                    "Failed parsing document: Start/end tag mismatch! "
+                            + "StartTag:" + startName + ", EndTag: "
+                            + endName);
+        }
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/DataElementParser.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/DataElementParser.java
new file mode 100644
index 0000000..401945b
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/DataElementParser.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import java.util.Iterator;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.DataMetadata;
+import edu.uci.ics.jung.io.graphml.GraphMLConstants;
+import edu.uci.ics.jung.io.graphml.ExceptionConverter;
+
+/**
+ * Parses the data element.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class DataElementParser<G extends Hypergraph<V,E>,V,E> extends AbstractElementParser<G,V,E> {
+
+    public DataElementParser(ParserContext<G,V,E> parserContext) {
+        super(parserContext);
+    }
+    
+    public DataMetadata parse(XMLEventReader xmlEventReader, StartElement start)
+            throws GraphIOException {
+
+        try {
+            // Create the new port.
+            DataMetadata data = new DataMetadata();
+
+            // Parse the attributes.
+            @SuppressWarnings("unchecked")
+			Iterator<Attribute> iterator = start.getAttributes();
+            while (iterator.hasNext()) {
+                Attribute attribute = (Attribute) iterator.next();
+                String name = attribute.getName().getLocalPart();
+                String value = attribute.getValue();
+                if (data.getKey() == null && GraphMLConstants.KEY_NAME.equals(name)) {
+                    data.setKey(value);
+                }
+            }
+
+            // Make sure the key has been set.
+            if (data.getKey() == null) {
+                throw new GraphIOException(
+                        "Element 'data' is missing attribute 'key'");
+            }
+
+            while (xmlEventReader.hasNext()) {
+
+                XMLEvent event = xmlEventReader.nextEvent();
+                if (event.isStartElement()) {
+                    StartElement element = (StartElement) event;
+                        
+                    // Treat any child elements as unknown
+                    getUnknownParser().parse(xmlEventReader, element);
+                }
+                if (event.isCharacters()) {
+                    Characters characters = (Characters) event;
+                    data.setValue(characters.getData());
+                }
+                if (event.isEndElement()) {
+                    EndElement end = (EndElement) event;
+                    verifyMatch(start, end);
+                    break;
+                }
+            }
+
+            return data;
+            
+        } catch (Exception e) {
+            ExceptionConverter.convert(e);
+        }
+
+        return null;
+    }
+}
+
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/EdgeElementParser.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/EdgeElementParser.java
new file mode 100644
index 0000000..30335a7
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/EdgeElementParser.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import java.util.Iterator;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.*;
+
+/**
+ * Parses an edge element.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class EdgeElementParser<G extends Hypergraph<V,E>,V,E> extends AbstractElementParser<G,V,E> {
+    
+    public EdgeElementParser(ParserContext<G,V,E> parserContext) {
+        super(parserContext);
+    }
+    
+    public EdgeMetadata parse(XMLEventReader xmlEventReader, StartElement start)
+            throws GraphIOException {
+
+        try {
+            // Create the new edge.
+            EdgeMetadata edge = new EdgeMetadata();
+
+            // Parse the attributes.
+            @SuppressWarnings("unchecked")
+			Iterator<Attribute> iterator = start.getAttributes();
+            while (iterator.hasNext()) {
+                Attribute attribute = iterator.next();
+                String name = attribute.getName().getLocalPart();
+                String value = attribute.getValue();
+                if (edge.getId() == null && GraphMLConstants.ID_NAME.equals(name)) {
+                    edge.setId(value);
+                } else if (edge.isDirected() == null && GraphMLConstants.DIRECTED_NAME.equals(name)) {
+                    edge.setDirected(("true".equals(value)));
+                } else if (edge.getSource() == null && GraphMLConstants.SOURCE_NAME.equals(name)) {
+                    edge.setSource(value);
+                } else if (edge.getTarget() == null && GraphMLConstants.TARGET_NAME.equals(name)) {
+                    edge.setTarget(value);
+                } else if (edge.getSourcePort() == null && GraphMLConstants.SOURCEPORT_NAME.equals(name)) {
+                    edge.setSourcePort(value);
+                } else if (edge.getTargetPort() == null && GraphMLConstants.TARGETPORT_NAME.equals(name)) {
+                    edge.setTargetPort(value);
+                } else {
+                    edge.setProperty(name, value);
+                }
+            }
+
+            // Make sure the source and target have been been set.
+            if (edge.getSource() == null || edge.getTarget() == null) {
+                throw new GraphIOException(
+                        "Element 'edge' is missing attribute 'source' or 'target'");
+            }
+
+            while (xmlEventReader.hasNext()) {
+
+                XMLEvent event = xmlEventReader.nextEvent();
+                if (event.isStartElement()) {
+                    StartElement element = (StartElement) event;
+
+                    String name = element.getName().getLocalPart();
+                    if(GraphMLConstants.DESC_NAME.equals(name)) {
+                        String desc = (String)getParser(name).parse(xmlEventReader, element);
+                        edge.setDescription(desc);
+                    } else if(GraphMLConstants.DATA_NAME.equals(name)) {
+                        DataMetadata data = (DataMetadata)getParser(name).parse(xmlEventReader, element);
+                        edge.addData(data);
+                    } else {
+                        
+                        // Treat anything else as unknown
+                        getUnknownParser().parse(xmlEventReader, element);
+                    }
+
+                }
+                if (event.isEndElement()) {
+                    EndElement end = (EndElement) event;
+                    verifyMatch(start, end);
+                    break;
+                }
+            }
+            
+            // Apply the keys to this object.
+            applyKeys(edge);
+
+            return edge;
+            
+        } catch (Exception e) {
+            ExceptionConverter.convert(e);
+        }
+
+        return null;
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/ElementParser.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/ElementParser.java
new file mode 100644
index 0000000..fecbfc5
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/ElementParser.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+
+import edu.uci.ics.jung.io.GraphIOException;
+
+/**
+ * Interface for all element parsers.  All parsers will be registered with the registry.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ * 
+ * @see ElementParserRegistry
+ */
+public interface ElementParser {
+    Object parse(XMLEventReader xmlEventReader, StartElement start)
+            throws GraphIOException;
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/ElementParserRegistry.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/ElementParserRegistry.java
new file mode 100644
index 0000000..5f2f748
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/ElementParserRegistry.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.io.graphml.EdgeMetadata;
+import edu.uci.ics.jung.io.graphml.GraphMLConstants;
+import edu.uci.ics.jung.io.graphml.GraphMetadata;
+import edu.uci.ics.jung.io.graphml.HyperEdgeMetadata;
+import edu.uci.ics.jung.io.graphml.KeyMap;
+import edu.uci.ics.jung.io.graphml.NodeMetadata;
+
+/**
+ * Registry for all element parsers.
+ * 
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class ElementParserRegistry<G extends Hypergraph<V, E>, V, E> {
+
+    final private Map<String, ElementParser> parserMap = new HashMap<String, ElementParser>();
+
+    final private ElementParser unknownElementParser = new UnknownElementParser();
+
+    public ElementParserRegistry(KeyMap keyMap, 
+            Function<GraphMetadata, G> graphTransformer,
+            Function<NodeMetadata, V> vertexTransformer,
+            Function<EdgeMetadata, E> edgeTransformer,
+            Function<HyperEdgeMetadata, E> hyperEdgeTransformer) {
+        
+        // Create the parser context.
+        ParserContext<G,V,E> context = new ParserContext<G,V,E>(this, keyMap, graphTransformer, 
+                vertexTransformer, edgeTransformer, hyperEdgeTransformer);
+    
+        parserMap.put(GraphMLConstants.DEFAULT_NAME, new StringElementParser<G,V,E>(context));
+        parserMap.put(GraphMLConstants.DESC_NAME, new StringElementParser<G,V,E>(context));
+        parserMap.put(GraphMLConstants.KEY_NAME, new KeyElementParser<G,V,E>(context));
+        parserMap.put(GraphMLConstants.DATA_NAME, new DataElementParser<G,V,E>(context));
+        parserMap.put(GraphMLConstants.PORT_NAME, new PortElementParser<G,V,E>(context));
+        parserMap.put(GraphMLConstants.NODE_NAME, new NodeElementParser<G,V,E>(context));
+        parserMap.put(GraphMLConstants.GRAPH_NAME, new GraphElementParser<G,V,E>(context));
+        parserMap.put(GraphMLConstants.ENDPOINT_NAME, new EndpointElementParser<G,V,E>(context));
+        parserMap.put(GraphMLConstants.EDGE_NAME, new EdgeElementParser<G,V,E>(context));
+        parserMap.put(GraphMLConstants.HYPEREDGE_NAME, new HyperEdgeElementParser<G,V,E>(context));
+    }
+
+    public ElementParser getUnknownElementParser() {
+        return unknownElementParser;
+    }
+
+    public ElementParser getParser(String localName) {
+        ElementParser parser = parserMap.get(localName);
+        if (parser == null) {
+            parser = unknownElementParser;
+        }
+
+        return parser;
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/EndpointElementParser.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/EndpointElementParser.java
new file mode 100644
index 0000000..9573474
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/EndpointElementParser.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.EndpointMetadata;
+import edu.uci.ics.jung.io.graphml.GraphMLConstants;
+import edu.uci.ics.jung.io.graphml.ExceptionConverter;
+import edu.uci.ics.jung.io.graphml.EndpointMetadata.EndpointType;
+
+/**
+ * Parses endpoint elements.
+ * 
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class EndpointElementParser<G extends Hypergraph<V,E>,V,E> extends AbstractElementParser<G,V,E> {
+    
+    final static private Map<String, EndpointType> endpointTypeMap = new HashMap<String, EndpointType>();
+    static {
+        endpointTypeMap.put(GraphMLConstants.IN_NAME, EndpointType.IN);
+        endpointTypeMap.put(GraphMLConstants.OUT_NAME, EndpointType.OUT);
+        endpointTypeMap.put(GraphMLConstants.UNDIR_NAME, EndpointType.UNDIR);
+    }
+    
+    public EndpointElementParser(ParserContext<G,V,E> parserContext) {
+        super(parserContext);
+    }
+    
+    public EndpointMetadata parse(XMLEventReader xmlEventReader, StartElement start)
+            throws GraphIOException {
+
+        try {
+            // Create the new endpoint.
+            EndpointMetadata endpoint = new EndpointMetadata();
+
+            // Parse the attributes.
+            @SuppressWarnings("unchecked")
+            Iterator<Attribute> iterator = start.getAttributes();
+            while (iterator.hasNext()) {
+                Attribute attribute = iterator.next();
+                String name = attribute.getName().getLocalPart();
+                String value = attribute.getValue();
+                if (endpoint.getId() == null && GraphMLConstants.ID_NAME.equals(name)) {
+                    endpoint.setId(value);
+                } if (endpoint.getPort() == null && GraphMLConstants.PORT_NAME.equals(name)) {
+                    endpoint.setPort(value);
+                } if (endpoint.getNode() == null && GraphMLConstants.NODE_NAME.equals(name)) {
+                    endpoint.setNode(value);
+                } if (GraphMLConstants.TYPE_NAME.equals(name)) {
+                    EndpointType t = endpointTypeMap.get(value);
+                    if( t == null ) {
+                        t = EndpointType.UNDIR;
+                    }
+                    endpoint.setEndpointType(t);
+                } else {
+                    endpoint.setProperty(name, value);
+                }
+            }
+
+            // Make sure the node has been set.
+            if (endpoint.getNode() == null) {
+                throw new GraphIOException(
+                        "Element 'endpoint' is missing attribute 'node'");
+            }
+
+            while (xmlEventReader.hasNext()) {
+
+                XMLEvent event = xmlEventReader.nextEvent();
+                if (event.isStartElement()) {
+                    StartElement element = (StartElement) event;
+
+                    String name = element.getName().getLocalPart();
+                    if(GraphMLConstants.DESC_NAME.equals(name)) {
+                        String desc = (String)getParser(name).parse(xmlEventReader, element);
+                        endpoint.setDescription(desc);
+                    } else {
+                        
+                        // Treat anything else as unknown
+                        getUnknownParser().parse(xmlEventReader, element);
+                    }
+
+                }
+                if (event.isEndElement()) {
+                    EndElement end = (EndElement) event;
+                    verifyMatch(start, end);
+                    break;
+                }
+            }
+            
+            // Apply the keys to this object.
+            applyKeys(endpoint);
+
+            return endpoint;
+            
+        } catch (Exception e) {
+            ExceptionConverter.convert(e);
+        }
+
+        return null;
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/GraphElementParser.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/GraphElementParser.java
new file mode 100644
index 0000000..b398bde
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/GraphElementParser.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.*;
+import edu.uci.ics.jung.io.graphml.GraphMetadata.EdgeDefault;
+
+/**
+ * Parses graph elements.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class GraphElementParser<G extends Hypergraph<V,E>,V,E> extends AbstractElementParser<G,V,E> {
+
+    public GraphElementParser(ParserContext<G,V,E> parserContext) {
+        super(parserContext);
+    }
+
+    public GraphMetadata parse(XMLEventReader xmlEventReader, StartElement start)
+            throws GraphIOException {
+
+        try {
+            // Create the new graph.
+            GraphMetadata graphMetadata = new GraphMetadata();
+
+            // Parse the attributes.
+            @SuppressWarnings("unchecked")
+            Iterator<Attribute> iterator = start.getAttributes();
+            while (iterator.hasNext()) {
+                Attribute attribute = iterator.next();
+                String name = attribute.getName().getLocalPart();
+                String value = attribute.getValue();
+                if (graphMetadata.getId() == null
+                        && GraphMLConstants.ID_NAME.equals(name)) {
+                    
+                    graphMetadata.setId(value);
+                } else if (graphMetadata.getEdgeDefault() == null
+                        && GraphMLConstants.EDGEDEFAULT_NAME.equals(name)) {
+                    
+                    graphMetadata.setEdgeDefault(GraphMLConstants.DIRECTED_NAME
+                            .equals(value) ? EdgeDefault.DIRECTED
+                            : EdgeDefault.UNDIRECTED);
+                } else {
+                    graphMetadata.setProperty(name, value);
+                }
+            }
+
+            // Make sure the graphdefault has been set.
+            if (graphMetadata.getEdgeDefault() == null) {
+                throw new GraphIOException(
+                        "Element 'graph' is missing attribute 'edgedefault'");
+            }
+            
+            Map<String, V> idToVertexMap = new HashMap<String, V>();
+            Collection<EdgeMetadata> edgeMetadata = new LinkedList<EdgeMetadata>();
+            Collection<HyperEdgeMetadata> hyperEdgeMetadata = new LinkedList<HyperEdgeMetadata>();
+
+            while (xmlEventReader.hasNext()) {
+
+                XMLEvent event = xmlEventReader.nextEvent();
+                if (event.isStartElement()) {
+                    StartElement element = (StartElement) event;
+
+                    String name = element.getName().getLocalPart();
+                    if (GraphMLConstants.DESC_NAME.equals(name)) {
+                        
+                        // Parse the description and set it in the graph.
+                        String desc = (String) getParser(name).parse(
+                                xmlEventReader, element);
+                        graphMetadata.setDescription(desc);
+                        
+                    } else if (GraphMLConstants.DATA_NAME.equals(name)) {
+                        
+                        // Parse the data element and store the property in the graph.
+                        DataMetadata data = (DataMetadata) getParser(name).parse(
+                                xmlEventReader, element);
+                        graphMetadata.addData(data);
+                        
+                    } else if (GraphMLConstants.NODE_NAME.equals(name)) {
+                        
+                        // Parse the node metadata
+                        NodeMetadata metadata = (NodeMetadata) getParser(name).parse(
+                                xmlEventReader, element);
+                        
+                        // Create the vertex object and store it in the metadata
+                        V vertex = getParserContext().createVertex(metadata);
+                        metadata.setVertex(vertex);
+                        idToVertexMap.put(metadata.getId(), vertex);
+                        
+                        // Add it to the graph
+                        graphMetadata.addNodeMetadata(vertex, metadata);
+                        
+                    } else if (GraphMLConstants.EDGE_NAME.equals(name)) {
+                        
+                        // Parse the edge metadata
+                        EdgeMetadata metadata = (EdgeMetadata) getParser(name).parse(
+                                xmlEventReader, element);
+                        
+                        // Set the directed property if not overridden.
+                        if (metadata.isDirected() == null) {
+                            metadata.setDirected(graphMetadata.getEdgeDefault() == EdgeDefault.DIRECTED);
+                        }
+                        
+                        // Create the edge object and store it in the metadata
+                        E edge = getParserContext().createEdge(metadata);
+                        edgeMetadata.add(metadata);
+                        metadata.setEdge(edge);
+                        
+                        // Add it to the graph.
+                        graphMetadata.addEdgeMetadata(edge, metadata);
+                        
+                    } else if (GraphMLConstants.HYPEREDGE_NAME.equals(name)) {
+                        
+                        // Parse the edge metadata
+                        HyperEdgeMetadata metadata = (HyperEdgeMetadata) getParser(name).parse(
+                                xmlEventReader, element);
+                        
+                        // Create the edge object and store it in the metadata
+                        E edge = getParserContext().createHyperEdge(metadata);
+                        hyperEdgeMetadata.add(metadata);
+                        metadata.setEdge(edge);
+                        
+                        // Add it to the graph
+                        graphMetadata.addHyperEdgeMetadata(edge, metadata);
+                        
+                    } else {
+
+                        // Treat anything else as unknown
+                        getUnknownParser().parse(xmlEventReader, element);
+                    }
+
+                }
+                if (event.isEndElement()) {
+                    EndElement end = (EndElement) event;
+                    verifyMatch(start, end);
+                    break;
+                }
+            }
+            
+            // Apply the keys to this object.
+            applyKeys(graphMetadata);   
+            
+            // Create the graph object and store it in the metadata
+            G graph = getParserContext().createGraph(graphMetadata);
+            graphMetadata.setGraph(graph);
+            
+            // Add all of the vertices to the graph object.
+            addVerticesToGraph(graph, idToVertexMap.values());
+            
+            // Add the edges to the graph object.
+            addEdgesToGraph(graph, edgeMetadata, idToVertexMap);
+            addHyperEdgesToGraph(graph, hyperEdgeMetadata, idToVertexMap);
+
+            return graphMetadata;
+
+        } catch (Exception e) {
+            ExceptionConverter.convert(e);
+        }
+
+        return null;
+    }
+    
+    private void addVerticesToGraph(G graph, Collection<V> vertices) {
+        
+        for (V vertex : vertices) {
+            graph.addVertex(vertex);
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    private void addEdgesToGraph(G graph, Collection<EdgeMetadata> metadata, 
+            Map<String,V> idToVertexMap) throws GraphIOException {
+        
+        for (EdgeMetadata emd : metadata) {
+
+            // Get the edge out of the metadata
+            E edge = (E)emd.getEdge();
+            
+            // Get the verticies.
+            V source = idToVertexMap.get(emd.getSource());
+            V target = idToVertexMap.get(emd.getTarget());
+            if (source == null || target == null) {
+                throw new GraphIOException(
+                        "edge references undefined source or target vertex. "
+                                + "Source: " + emd.getSource()
+                                + ", Target: " + emd.getTarget());
+            }
+
+            // Add it to the graph.
+            if (graph instanceof Graph) {
+                ((Graph<V, E>) graph).addEdge(edge, source, target, emd
+                        .isDirected() ? EdgeType.DIRECTED
+                        : EdgeType.UNDIRECTED);
+            } else {
+                graph.addEdge(edge, new Pair<V>(source, target));
+            }
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    private void addHyperEdgesToGraph(G graph, Collection<HyperEdgeMetadata> metadata, 
+            Map<String,V> idToVertexMap) throws GraphIOException {
+        
+        for (HyperEdgeMetadata emd : metadata) {
+
+            // Get the edge out of the metadata
+            E edge = (E)emd.getEdge();
+            
+            // Add the verticies to a list.
+            List<V> verticies = new ArrayList<V>();
+            List<EndpointMetadata> endpoints = emd.getEndpoints();
+            for (EndpointMetadata ep : endpoints) {
+                V v = idToVertexMap.get(ep.getNode());
+                if (v == null) {
+                    throw new GraphIOException(
+                            "hyperedge references undefined vertex: "
+                                    + ep.getNode());
+                }
+                verticies.add(v);
+            }
+
+            // Add it to the graph.
+            graph.addEdge(edge, verticies);
+        }
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/GraphMLEventFilter.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/GraphMLEventFilter.java
new file mode 100644
index 0000000..e1b5703
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/GraphMLEventFilter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import javax.xml.stream.EventFilter;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.events.XMLEvent;
+
+/**
+ * Filter to ignore unsupported XML events.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class GraphMLEventFilter implements EventFilter {    
+
+    public boolean accept(XMLEvent event) {
+        switch( event.getEventType() ) {
+        case XMLStreamConstants.START_ELEMENT:                        
+        case XMLStreamConstants.END_ELEMENT:
+        case XMLStreamConstants.CHARACTERS:
+        case XMLStreamConstants.ATTRIBUTE:
+        case XMLStreamConstants.NAMESPACE:
+        case XMLStreamConstants.START_DOCUMENT:
+        case XMLStreamConstants.END_DOCUMENT: {
+            return true;
+        }
+        default: {
+            return false;
+        }
+        }
+    }
+    
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/HyperEdgeElementParser.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/HyperEdgeElementParser.java
new file mode 100644
index 0000000..259bcf6
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/HyperEdgeElementParser.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import java.util.Iterator;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.*;
+
+/**
+ * Parses hyper edge elements.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class HyperEdgeElementParser<G extends Hypergraph<V,E>,V,E> extends AbstractElementParser<G,V,E> {
+
+    public HyperEdgeElementParser(ParserContext<G,V,E> parserContext) {
+        super(parserContext);
+    }
+    
+    public HyperEdgeMetadata parse(XMLEventReader xmlEventReader, StartElement start)
+            throws GraphIOException {
+
+        try {
+            // Create the new edge.
+            HyperEdgeMetadata edge = new HyperEdgeMetadata();
+
+            // Parse the attributes.
+            @SuppressWarnings("unchecked")
+            Iterator<Attribute> iterator = start.getAttributes();
+            while (iterator.hasNext()) {
+                Attribute attribute = (Attribute) iterator.next();
+                String name = attribute.getName().getLocalPart();
+                String value = attribute.getValue();
+                if (edge.getId() == null && GraphMLConstants.ID_NAME.equals(name)) {
+                    edge.setId(value);
+                } else {
+                    edge.setProperty(name, value);
+                }
+            }
+
+            while (xmlEventReader.hasNext()) {
+
+                XMLEvent event = xmlEventReader.nextEvent();
+                if (event.isStartElement()) {
+                    StartElement element = (StartElement) event;
+
+                    String name = element.getName().getLocalPart();
+                    if(GraphMLConstants.DESC_NAME.equals(name)) {
+                        String desc = (String)getParser(name).parse(xmlEventReader, element);
+                        edge.setDescription(desc);
+                    } else if(GraphMLConstants.DATA_NAME.equals(name)) {
+                        DataMetadata data = (DataMetadata)getParser(name).parse(xmlEventReader, element);
+                        edge.addData(data);
+                    } else if(GraphMLConstants.ENDPOINT_NAME.equals(name)) {
+                        EndpointMetadata ep = (EndpointMetadata)getParser(name).parse(xmlEventReader, element);
+                        edge.addEndpoint(ep);
+                    } else {
+                        
+                        // Treat anything else as unknown
+                        getUnknownParser().parse(xmlEventReader, element);
+                    }
+
+                }
+                if (event.isEndElement()) {
+                    EndElement end = (EndElement) event;
+                    verifyMatch(start, end);
+                    break;
+                }
+            }
+
+            // Apply the keys to this object.
+            applyKeys(edge);
+            
+            return edge;
+            
+        } catch (Exception e) {
+            ExceptionConverter.convert(e);
+        }
+
+        return null;
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/KeyElementParser.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/KeyElementParser.java
new file mode 100644
index 0000000..a3a3736
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/KeyElementParser.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import java.util.Iterator;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.GraphMLConstants;
+import edu.uci.ics.jung.io.graphml.Key;
+import edu.uci.ics.jung.io.graphml.ExceptionConverter;
+
+/**
+ * Parses key elements.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class KeyElementParser<G extends Hypergraph<V,E>,V,E> extends AbstractElementParser<G,V,E> {
+
+    public KeyElementParser(ParserContext<G,V,E> parserContext) {
+        super(parserContext);
+    }
+    
+    public Key parse(XMLEventReader xmlEventReader, StartElement start)
+            throws GraphIOException {
+
+        try {
+            // Create the new key. ForType defaults to ALL.
+            Key key = new Key();
+
+            // Parse the attributes.
+            @SuppressWarnings("unchecked")
+            Iterator<Attribute> iterator = start.getAttributes();
+            while (iterator.hasNext()) {
+                Attribute attribute = iterator.next();
+                String name = attribute.getName().getLocalPart();
+                String value = attribute.getValue();
+                if (key.getId() == null && GraphMLConstants.ID_NAME.equals(name)) {
+                    key.setId(value);
+                } else if (key.getAttributeName() == null
+                        && GraphMLConstants.ATTRNAME_NAME.equals(name)) {
+                    key.setAttributeName(value);
+                } else if (key.getAttributeType() == null
+                        && GraphMLConstants.ATTRTYPE_NAME.equals(name)) {
+                    key.setAttributeType(value);
+                } else if (GraphMLConstants.FOR_NAME.equals(name)) {
+                    key.setForType(convertFor(value));
+                }
+            }
+
+            // Make sure the id has been set.
+            if (key.getId() == null) {
+                throw new GraphIOException(
+                        "Element 'key' is missing attribute 'id'");
+            }
+
+            while (xmlEventReader.hasNext()) {
+
+                XMLEvent event = xmlEventReader.nextEvent();
+                if (event.isStartElement()) {
+                    StartElement element = (StartElement) event;
+
+                    String name = element.getName().getLocalPart();
+                    if(GraphMLConstants.DESC_NAME.equals(name)) {
+                        String desc = (String)getParser(name).parse(xmlEventReader, element);
+                        key.setDescription(desc);
+                    } else if(GraphMLConstants.DEFAULT_NAME.equals(name)) {
+                        String defaultValue = (String)getParser(name).parse(xmlEventReader, element);
+                        key.setDefaultValue(defaultValue);
+                    } else {
+                        
+                        // Treat anything else as unknown
+                        getUnknownParser().parse(xmlEventReader, element);
+                    }
+
+                }
+                if (event.isEndElement()) {
+                    EndElement end = (EndElement) event;
+                    verifyMatch(start, end);
+                    break;
+                }
+            }
+
+            return key;
+            
+        } catch (Exception e) {
+            ExceptionConverter.convert(e);
+        }
+
+        return null;
+    }
+
+    static public Key.ForType convertFor(String value) {
+
+        if (value != null) {
+
+            if (GraphMLConstants.GRAPH_NAME.equals(value)) {
+                return Key.ForType.GRAPH;
+            }
+            if (GraphMLConstants.EDGE_NAME.equals(value)) {
+                return Key.ForType.EDGE;
+            }
+            if (GraphMLConstants.ENDPOINT_NAME.equals(value)) {
+                return Key.ForType.ENDPOINT;
+            }
+            if (GraphMLConstants.HYPEREDGE_NAME.equals(value)) {
+                return Key.ForType.HYPEREDGE;
+            }
+            if (GraphMLConstants.NODE_NAME.equals(value)) {
+                return Key.ForType.NODE;
+            }
+            if (GraphMLConstants.PORT_NAME.equals(value)) {
+                return Key.ForType.PORT;
+            }
+        }
+
+        return Key.ForType.ALL;
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/NodeElementParser.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/NodeElementParser.java
new file mode 100644
index 0000000..22d0706
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/NodeElementParser.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import java.util.Iterator;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.*;
+
+/**
+ * Parses node elements.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class NodeElementParser<G extends Hypergraph<V,E>,V,E> extends AbstractElementParser<G,V,E> {
+
+    public NodeElementParser(ParserContext<G,V,E> parserContext) {
+        super(parserContext);
+    }
+    
+    @SuppressWarnings("unchecked")
+    public NodeMetadata parse(XMLEventReader xmlEventReader, StartElement start)
+            throws GraphIOException {
+
+        try {
+            // Create the new node.
+            NodeMetadata node = new NodeMetadata();
+
+            // Parse the attributes.
+            Iterator<Attribute> iterator = start.getAttributes();
+            while (iterator.hasNext()) {
+                Attribute attribute = iterator.next();
+                String name = attribute.getName().getLocalPart();
+                String value = attribute.getValue();
+                if (node.getId() == null && GraphMLConstants.ID_NAME.equals(name)) {
+                    node.setId(value);
+                } else {
+                    node.setProperty(name, value);
+                }
+            }
+
+            // Make sure the name has been set.
+            if (node.getId() == null) {
+                throw new GraphIOException(
+                        "Element 'node' is missing attribute 'id'");
+            }
+
+            while (xmlEventReader.hasNext()) {
+
+                XMLEvent event = xmlEventReader.nextEvent();
+                if (event.isStartElement()) {
+                    StartElement element = (StartElement) event;
+
+                    String name = element.getName().getLocalPart();
+                    if(GraphMLConstants.DESC_NAME.equals(name)) {
+                        String desc = (String)getParser(name).parse(xmlEventReader, element);
+                        node.setDescription(desc);
+                    } else if(GraphMLConstants.DATA_NAME.equals(name)) {
+                        DataMetadata data = (DataMetadata)getParser(name).parse(xmlEventReader, element);
+                        node.addData(data);
+                    } else if(GraphMLConstants.PORT_NAME.equals(name)) {
+                        PortMetadata port = (PortMetadata)getParser(name).parse(xmlEventReader, element);
+                        node.addPort(port);
+                    } else {
+                        
+                        // Treat anything else as unknown
+                        getUnknownParser().parse(xmlEventReader, element);
+                    }
+
+                } else if (event.isEndElement()) {
+                    EndElement end = (EndElement) event;
+                    verifyMatch(start, end);
+                    break;
+                }
+            }
+            
+            // Apply the keys to this object.
+            applyKeys(node);
+
+            return node;
+            
+        } catch (Exception e) {
+            ExceptionConverter.convert(e);
+        }
+
+        return null;
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/ParserContext.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/ParserContext.java
new file mode 100644
index 0000000..df6852b
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/ParserContext.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.io.graphml.EdgeMetadata;
+import edu.uci.ics.jung.io.graphml.GraphMetadata;
+import edu.uci.ics.jung.io.graphml.HyperEdgeMetadata;
+import edu.uci.ics.jung.io.graphml.KeyMap;
+import edu.uci.ics.jung.io.graphml.NodeMetadata;
+
+/**
+ * Provides resources related to the current parsing context. 
+ * 
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ *
+ * @param <G> The graph type
+ * @param <V> The vertex type
+ * @param <E> The edge type
+ */
+public class ParserContext<G extends Hypergraph<V, E>, V, E> {
+
+    private final KeyMap keyMap;
+    private final ElementParserRegistry<G,V,E> elementParserRegistry;
+    private final Function<GraphMetadata, G> graphTransformer;
+    private final Function<NodeMetadata, V> vertexTransformer;
+    private final Function<EdgeMetadata, E> edgeTransformer;
+    private final Function<HyperEdgeMetadata, E> hyperEdgeTransformer;
+    
+    public ParserContext(ElementParserRegistry<G,V,E> elementParserRegistry, 
+            KeyMap keyMap,
+            Function<GraphMetadata, G> graphTransformer,
+            Function<NodeMetadata, V> vertexTransformer,
+            Function<EdgeMetadata, E> edgeTransformer,                        
+            Function<HyperEdgeMetadata, E> hyperEdgeTransformer ) {
+        this.elementParserRegistry = elementParserRegistry;
+        this.keyMap = keyMap;
+        this.graphTransformer = graphTransformer;
+        this.vertexTransformer = vertexTransformer;
+        this.edgeTransformer = edgeTransformer;                
+        this.hyperEdgeTransformer = hyperEdgeTransformer;                
+    }
+
+    public ElementParserRegistry<G,V,E> getElementParserRegistry() {
+        return elementParserRegistry;
+    }
+    
+    public KeyMap getKeyMap() {
+        return keyMap;
+    }
+    
+    public G createGraph(GraphMetadata metadata) {
+        return graphTransformer.apply(metadata);
+    }
+    
+    public V createVertex(NodeMetadata metadata) {
+        return vertexTransformer.apply(metadata);
+    }
+    
+    public E createEdge(EdgeMetadata metadata) {
+        return edgeTransformer.apply(metadata);
+    }
+    
+    public E createHyperEdge(HyperEdgeMetadata metadata) {
+        return hyperEdgeTransformer.apply(metadata);
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/PortElementParser.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/PortElementParser.java
new file mode 100644
index 0000000..532e708
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/PortElementParser.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import java.util.Iterator;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.*;
+
+/**
+ * Parses port elements.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class PortElementParser<G extends Hypergraph<V,E>,V,E> extends AbstractElementParser<G,V,E> {
+
+    public PortElementParser(ParserContext<G,V,E> parserContext) {
+        super(parserContext);
+    }
+    
+    public PortMetadata parse(XMLEventReader xmlEventReader, StartElement start)
+            throws GraphIOException {
+
+        try {
+            
+            // Create the new port.
+            PortMetadata port = new PortMetadata();
+
+            // Parse the attributes.
+            @SuppressWarnings("unchecked")
+            Iterator<Attribute> iterator = start.getAttributes();
+            while (iterator.hasNext()) {
+                Attribute attribute = iterator.next();
+                String name = attribute.getName().getLocalPart();
+                String value = attribute.getValue();
+                if (port.getName() == null && GraphMLConstants.NAME_NAME.equals(name)) {
+                    port.setName(value);
+                } else {
+                    port.setProperty(name, value);
+                }
+            }
+
+            // Make sure the name has been set.
+            if (port.getName() == null) {
+                throw new GraphIOException(
+                        "Element 'port' is missing attribute 'name'");
+            }
+
+            while (xmlEventReader.hasNext()) {
+
+                XMLEvent event = xmlEventReader.nextEvent();
+                if (event.isStartElement()) {
+                    StartElement element = (StartElement) event;
+
+                    String name = element.getName().getLocalPart();
+                    if(GraphMLConstants.DESC_NAME.equals(name)) {
+                        String desc = (String)getParser(name).parse(xmlEventReader, element);
+                        port.setDescription(desc);
+                    } else if(GraphMLConstants.DATA_NAME.equals(name)) {
+                        DataMetadata data = (DataMetadata)getParser(name).parse(xmlEventReader, element);
+                        port.addData(data);
+                    } else {
+                        
+                        // Treat anything else as unknown
+                        getUnknownParser().parse(xmlEventReader, element);
+                    }
+
+                }
+                if (event.isEndElement()) {
+                    EndElement end = (EndElement) event;
+                    verifyMatch(start, end);
+                    break;
+                }
+            }
+            
+            // Apply the keys to this port.
+            applyKeys(port);
+
+            return port;
+            
+        } catch (Exception e) {
+            ExceptionConverter.convert(e);
+        }
+
+        return null;
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/StringElementParser.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/StringElementParser.java
new file mode 100644
index 0000000..ac7e19b
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/StringElementParser.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.ExceptionConverter;
+
+/**
+ * Parses an element that just contains text.
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class StringElementParser<G extends Hypergraph<V,E>,V,E> extends AbstractElementParser<G,V,E> {
+
+    public StringElementParser(ParserContext<G,V,E> parserContext) {
+        super(parserContext);
+    }
+    
+    public String parse(XMLEventReader xmlEventReader, StartElement start)
+            throws GraphIOException {
+
+        try {
+            String str = null;
+
+            while (xmlEventReader.hasNext()) {
+
+                XMLEvent event = xmlEventReader.nextEvent();
+                if (event.isStartElement()) {
+
+                    // Parse the unknown element.
+                    getUnknownParser().parse(xmlEventReader, event
+                            .asStartElement());
+                } else if (event.isEndElement()) {
+                    EndElement end = (EndElement) event;
+                    verifyMatch(start, end);
+                    break;
+                } else if (event.isCharacters()) {
+                    Characters characters = (Characters) event;
+                    str = characters.getData();
+                }
+            }
+
+            return str;
+
+        } catch (Exception e) {
+            ExceptionConverter.convert(e);
+        }
+
+        return null;
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/UnknownElementParser.java b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/UnknownElementParser.java
new file mode 100644
index 0000000..42b2fc8
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/graphml/parser/UnknownElementParser.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import java.util.Stack;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.ExceptionConverter;
+
+/**
+ * Skips an entire unknown subtree of the XML
+ *
+ * @author Nathan Mittler - nathan.mittler at gmail.com
+ */
+public class UnknownElementParser implements ElementParser {
+
+    /**
+     * Skips an entire subtree starting with the provided unknown element.
+     * 
+     * @param xmlEventReader
+     *            the event reader
+     * @param start
+     *            the unknown element to be skipped.
+     * @return null
+     */
+    public Object parse(XMLEventReader xmlEventReader, StartElement start)
+            throws GraphIOException {
+
+        try {
+            Stack<String> skippedElements = new Stack<String>();
+            skippedElements.add(start.getName().getLocalPart());
+
+            while (xmlEventReader.hasNext()) {
+
+                XMLEvent event = xmlEventReader.nextEvent();
+                if (event.isStartElement()) {
+
+                    String name = event.asStartElement().getName()
+                            .getLocalPart();
+
+                    // Push the name of the unknown element.
+                    skippedElements.push(name);
+                }
+                if (event.isEndElement()) {
+
+                    String name = event.asEndElement().getName()
+                            .getLocalPart();
+
+                    if (skippedElements.size() == 0
+                            || !skippedElements.peek().equals(name)) {
+                        throw new GraphIOException(
+                                "Failed parsing GraphML document - startTag/endTag mismatch");
+                    }
+
+                    // Pop the stack.
+                    skippedElements.pop();                        
+                    if( skippedElements.isEmpty() ) {
+                        break;
+                    }
+                }
+            }
+
+            return null;
+
+        } catch (Exception e) {
+            ExceptionConverter.convert(e);
+        }
+
+        return null;
+    }
+}
diff --git a/jung-io/src/main/java/edu/uci/ics/jung/io/package.html b/jung-io/src/main/java/edu/uci/ics/jung/io/package.html
new file mode 100644
index 0000000..db3fb52
--- /dev/null
+++ b/jung-io/src/main/java/edu/uci/ics/jung/io/package.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Interfaces and classes for reading and writing graphs in various (file)
+formats.  Current formats fully or partially supported include:
+<ul>
+<li>GraphML format
+<li>Pajek NET format
+</ul>
+
+</body>
+</html>
diff --git a/jung-io/src/site/site.xml b/jung-io/src/site/site.xml
new file mode 100644
index 0000000..4f25bdf
--- /dev/null
+++ b/jung-io/src/site/site.xml
@@ -0,0 +1,14 @@
+<project name="${project.name}">
+  <bannerLeft>
+    <name>${project.name}</name>
+  </bannerLeft>
+  <body>
+    <links>
+      <item name="${project.name}" href="${project.url}"/>
+    </links>
+    <menu ref="parent" />
+    <menu ref="modules" />
+    <menu ref="reports" />
+  </body>
+</project>
+
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/PajekNetIOTest.java b/jung-io/src/test/java/edu/uci/ics/jung/io/PajekNetIOTest.java
new file mode 100644
index 0000000..bff2e67
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/PajekNetIOTest.java
@@ -0,0 +1,433 @@
+/*
+ * Created on May 3, 2004
+ *
+ * Copyright (c) 2004, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.io;
+
+import java.awt.geom.Point2D;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.UndirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+
+/**
+ * Needed tests:
+ * - edgeslist, arcslist
+ * - unit test to catch bug in readArcsOrEdges() [was skipping until e_pred, not c_pred]
+ * 
+ * @author Joshua O'Madadhain
+ * @author Tom Nelson - converted to jung2
+ */
+public class PajekNetIOTest extends TestCase
+{
+    protected String[] vertex_labels = {"alpha", "beta", "gamma", "delta", "epsilon"};
+    
+	Supplier<DirectedGraph<Number,Number>> directedGraphFactory;
+	Supplier<UndirectedGraph<Number,Number>> undirectedGraphFactory;
+	Supplier<Graph<Number,Number>> graphFactory;
+	Supplier<Number> vertexFactory;
+	Supplier<Number> edgeFactory;
+    PajekNetReader<Graph<Number, Number>, Number,Number> pnr; 
+	
+    @Override
+    protected void setUp() {
+    	directedGraphFactory = new Supplier<DirectedGraph<Number,Number>>() {
+    		public DirectedGraph<Number,Number> get() {
+    			return new DirectedSparseMultigraph<Number,Number>();
+    		}
+    	};
+    	undirectedGraphFactory = new Supplier<UndirectedGraph<Number,Number>>() {
+    		public UndirectedGraph<Number,Number> get() {
+    			return new UndirectedSparseMultigraph<Number,Number>();
+    		}
+    	};
+    	graphFactory = new Supplier<Graph<Number,Number>>() {
+    		public Graph<Number,Number> get() {
+    			return new SparseMultigraph<Number,Number>();
+    		}
+    	};
+    	vertexFactory = new Supplier<Number>() {
+    		int n = 0;
+    		public Number get() { return n++; }
+    	};
+    	edgeFactory = new Supplier<Number>() {
+    		int n = 0;
+    		public Number get() { return n++; }
+    	};
+        pnr = new PajekNetReader<Graph<Number, Number>, Number,Number>(vertexFactory, edgeFactory);
+
+    }
+
+    public void testNull()
+    {
+        
+    }
+    
+
+    public void testFileNotFound()
+    {
+        try
+        {
+            pnr.load("/dev/null/foo", graphFactory);
+            fail("File load did not fail on nonexistent file");
+        }
+        catch (FileNotFoundException fnfe)
+        {
+        }
+        catch (IOException ioe)
+        {
+            fail("unexpected IOException");
+        }
+    }
+
+    public void testNoLabels() throws IOException
+    {
+        String test = "*Vertices 3\n1\n2\n3\n*Edges\n1 2\n2 2";
+        Reader r = new StringReader(test);
+        
+        Graph<Number, Number> g = pnr.load(r, undirectedGraphFactory);
+        assertEquals(g.getVertexCount(), 3);
+        assertEquals(g.getEdgeCount(), 2);
+    }
+    
+    public void testDirectedSaveLoadSave() throws IOException
+    {
+        Graph<Number,Number> graph1 = directedGraphFactory.get();
+        for(int i=0; i<5; i++) {
+        	graph1.addVertex(i);
+        }
+//        GraphUtils.addVertices(graph1, 5);
+        List<Number> id = new ArrayList<Number>(graph1.getVertices());//Indexer.getIndexer(graph1);
+        GreekLabels<Number> gl = new GreekLabels<Number>(id);
+        int j=0;
+        graph1.addEdge(j++, 0, 1);
+        graph1.addEdge(j++, 0, 2);
+        graph1.addEdge(j++, 1, 2);
+        graph1.addEdge(j++, 1, 3);
+        graph1.addEdge(j++, 1, 4);
+        graph1.addEdge(j++, 4, 3);
+        
+        
+//        System.err.println("graph1 = "+graph1);
+//        for(Number edge : graph1.getEdges()) {
+//        	System.err.println("edge "+edge+" is directed? "+graph1.getEdgeType(edge));
+//        }
+//        for(Number v : graph1.getVertices()) {
+//        	System.err.println(v+" outedges are "+graph1.getOutEdges(v));
+//        	System.err.println(v+" inedges are "+graph1.getInEdges(v));
+//        	System.err.println(v+" incidentedges are "+graph1.getIncidentEdges(v));
+//        }
+
+        assertEquals(graph1.getEdgeCount(), 6);
+
+        String testFilename = "dtest.net";
+        String testFilename2 = testFilename + "2";
+
+        PajekNetWriter<Number,Number> pnw = new PajekNetWriter<Number,Number>();
+        pnw.save(graph1, testFilename, gl, null, null);
+
+        Graph<Number,Number> graph2 = pnr.load(testFilename, directedGraphFactory);
+
+//        System.err.println("graph2 = "+graph2);
+//        for(Number edge : graph2.getEdges()) {
+//        	System.err.println("edge "+edge+" is directed? "+graph2.getEdgeType(edge));
+//        }
+//        for(Number v : graph2.getVertices()) {
+//        	System.err.println(v+" outedges are "+graph2.getOutEdges(v));
+//        	System.err.println(v+" inedges are "+graph2.getInEdges(v));
+//        	System.err.println(v+" incidentedges are "+graph2.getIncidentEdges(v));
+//       }
+
+        assertEquals(graph1.getVertexCount(), graph2.getVertexCount());
+        assertEquals(graph1.getEdgeCount(), graph2.getEdgeCount());
+
+        pnw.save(graph2, testFilename2, pnr.getVertexLabeller(), null, null);
+
+        compareIndexedGraphs(graph1, graph2);
+
+        Graph<Number,Number> graph3 = pnr.load(testFilename2, graphFactory);
+
+//        System.err.println("graph3 = "+graph3);
+//        for(Number edge : graph3.getEdges()) {
+//        	System.err.println("edge "+edge+" is directed? "+graph3.getEdgeType(edge));
+//        }
+//        for(Number v : graph3.getVertices()) {
+//        	System.err.println(v+" outedges are "+graph3.getOutEdges(v));
+//        	System.err.println(v+" inedges are "+graph3.getInEdges(v));
+//        	System.err.println(v+" incidentedges are "+graph3.getIncidentEdges(v));
+//        }
+
+        compareIndexedGraphs(graph2, graph3);
+
+        File file1 = new File(testFilename);
+        File file2 = new File(testFilename2);
+
+        Assert.assertTrue(file1.length() == file2.length());
+        file1.delete();
+        file2.delete();
+    }
+
+    public void testUndirectedSaveLoadSave() throws IOException
+    {
+        UndirectedGraph<Number,Number> graph1 = 
+        	undirectedGraphFactory.get();
+        for(int i=0; i<5; i++) {
+        	graph1.addVertex(i);
+        }
+
+        List<Number> id = new ArrayList<Number>(graph1.getVertices());
+        int j=0;
+        GreekLabels<Number> gl = new GreekLabels<Number>(id);
+        graph1.addEdge(j++, 0, 1);
+        graph1.addEdge(j++, 0, 2);
+        graph1.addEdge(j++, 1, 2);
+        graph1.addEdge(j++, 1, 3);
+        graph1.addEdge(j++, 1, 4);
+        graph1.addEdge(j++, 4, 3);
+
+        assertEquals(graph1.getEdgeCount(), 6);
+
+//        System.err.println("graph1 = "+graph1);
+//        for(Number edge : graph1.getEdges()) {
+//        	System.err.println("edge "+edge+" is directed? "+graph1.getEdgeType(edge));
+//        }
+//        for(Number v : graph1.getVertices()) {
+//        	System.err.println(v+" outedges are "+graph1.getOutEdges(v));
+//        	System.err.println(v+" inedges are "+graph1.getInEdges(v));
+//        	System.err.println(v+" incidentedges are "+graph1.getIncidentEdges(v));
+//        }
+
+        String testFilename = "utest.net";
+        String testFilename2 = testFilename + "2";
+
+        PajekNetWriter<Number,Number> pnw = new PajekNetWriter<Number,Number>();
+        pnw.save(graph1, testFilename, gl, null, null);
+
+        Graph<Number,Number> graph2 = pnr.load(testFilename, undirectedGraphFactory);
+        
+        
+//        System.err.println("graph2 = "+graph2);
+//        for(Number edge : graph2.getEdges()) {
+//        	System.err.println("edge "+edge+" is directed? "+graph2.getEdgeType(edge));
+//        }
+//        for(Number v : graph2.getVertices()) {
+//        	System.err.println(v+" outedges are "+graph2.getOutEdges(v));
+//        	System.err.println(v+" inedges are "+graph2.getInEdges(v));
+//        	System.err.println(v+" incidentedges are "+graph2.getIncidentEdges(v));
+//        }
+
+
+        assertEquals(graph1.getVertexCount(), graph2.getVertexCount());
+        assertEquals(graph1.getEdgeCount(), graph2.getEdgeCount());
+
+        pnw.save(graph2, testFilename2, pnr.getVertexLabeller(), null, null);
+        compareIndexedGraphs(graph1, graph2);
+
+        Graph<Number,Number> graph3 = pnr.load(testFilename2, graphFactory);
+//        System.err.println("graph3 = "+graph3);
+//        for(Number edge : graph3.getEdges()) {
+//        	System.err.println("edge "+edge+" is directed? "+graph3.getEdgeType(edge));
+//        }
+//        for(Number v : graph3.getVertices()) {
+//        	System.err.println(v+" outedges are "+graph3.getOutEdges(v));
+//        	System.err.println(v+" inedges are "+graph3.getInEdges(v));
+//        	System.err.println(v+" incidentedges are "+graph3.getIncidentEdges(v));
+//        }
+
+        compareIndexedGraphs(graph2, graph3);
+
+        File file1 = new File(testFilename);
+        File file2 = new File(testFilename2);
+
+        Assert.assertTrue(file1.length() == file2.length());
+        file1.delete();
+        file2.delete();
+    }
+
+    public void testMixedSaveLoadSave() throws IOException
+    {
+        Graph<Number,Number> graph1 = new SparseMultigraph<Number,Number>();
+        for(int i=0; i<5; i++) {
+        	graph1.addVertex(i);
+        }
+        int j=0;
+
+        List<Number> id = new ArrayList<Number>(graph1.getVertices());
+        GreekLabels<Number> gl = new GreekLabels<Number>(id);
+        Number[] edges = { 0,1,2,3,4,5 };
+
+        graph1.addEdge(j++, 0, 1, EdgeType.DIRECTED);
+        graph1.addEdge(j++, 0, 2, EdgeType.DIRECTED);
+        graph1.addEdge(j++, 1, 2, EdgeType.DIRECTED);
+        graph1.addEdge(j++, 1, 3);
+        graph1.addEdge(j++, 1, 4);
+        graph1.addEdge(j++, 4, 3);
+
+        Map<Number,Number> nr = new HashMap<Number,Number>();
+        for (int i = 0; i < edges.length; i++)
+        {
+            nr.put(edges[i], new Float(Math.random()));
+        }
+        
+        assertEquals(graph1.getEdgeCount(), 6);
+
+//        System.err.println(" mixed graph1 = "+graph1);
+//        for(Number edge : graph1.getEdges()) {
+//        	System.err.println("edge "+edge+" is directed? "+graph1.getEdgeType(edge));
+//        }
+//        for(Number v : graph1.getVertices()) {
+//        	System.err.println(v+" outedges are "+graph1.getOutEdges(v));
+//        	System.err.println(v+" inedges are "+graph1.getInEdges(v));
+//        	System.err.println(v+" incidentedges are "+graph1.getIncidentEdges(v));
+//        }
+
+        String testFilename = "mtest.net";
+        String testFilename2 = testFilename + "2";
+
+        // assign arbitrary locations to each vertex
+        Map<Number, Point2D> locations = new HashMap<Number, Point2D>();
+        for (Number v : graph1.getVertices()) {
+        	locations.put(v, new Point2D.Double(v.intValue() * v.intValue(), 1 << v.intValue()));
+        }
+        Function<Number, Point2D> vld = Functions.forMap(locations);
+        
+        PajekNetWriter<Number,Number> pnw = new PajekNetWriter<Number,Number>();
+        pnw.save(graph1, testFilename, gl, Functions.forMap(nr), vld);
+        
+        Graph<Number,Number> graph2 = pnr.load(testFilename, graphFactory);
+        Function<Number, String> pl = pnr.getVertexLabeller();
+        List<Number> id2 = new ArrayList<Number>(graph2.getVertices());
+        Function<Number,Point2D> vld2 = pnr.getVertexLocationTransformer();
+        
+        assertEquals(graph1.getVertexCount(), graph2.getVertexCount());
+        assertEquals(graph1.getEdgeCount(), graph2.getEdgeCount());
+
+        // test vertex labels and locations
+        for (int i = 0; i < graph1.getVertexCount(); i++)
+        {
+            Number v1 = id.get(i);
+            Number v2 = id2.get(i);
+            assertEquals(gl.apply(v1), pl.apply(v2));
+            assertEquals(vld.apply(v1), vld2.apply(v2));
+        }
+        
+        // test edge weights
+        Function<Number,Number> nr2 = pnr.getEdgeWeightTransformer();
+        for (Number e2 : graph2.getEdges()) 
+        {
+            Pair<Number> endpoints = graph2.getEndpoints(e2);
+            Number v1_2 = endpoints.getFirst();
+            Number v2_2 = endpoints.getSecond();
+            Number v1_1 = id.get(id2.indexOf(v1_2));
+            Number v2_1 = id.get(id2.indexOf(v2_2));
+            Number e1 = graph1.findEdge(v1_1, v2_1);
+            assertNotNull(e1);
+            assertEquals(nr.get(e1).floatValue(), nr2.apply(e2).floatValue(), 0.0001);
+        }
+
+        pnw.save(graph2, testFilename2, pl, nr2, vld2);
+
+        compareIndexedGraphs(graph1, graph2);
+
+        pnr.setVertexLabeller(null);
+        Graph<Number,Number> graph3 = pnr.load(testFilename2, graphFactory);
+
+        compareIndexedGraphs(graph2, graph3);
+
+        File file1 = new File(testFilename);
+        File file2 = new File(testFilename2);
+
+        Assert.assertTrue(file1.length() == file2.length());
+        file1.delete();
+        file2.delete();
+        
+    }
+
+    
+    /**
+     * Tests to see whether these two graphs are structurally equivalent, based
+     * on the connectivity of the vertices with matching indices in each graph.
+     * Assumes a 0-based index. 
+     * 
+     * @param g1
+     * @param g2
+     */
+    private void compareIndexedGraphs(Graph<Number,Number> g1, Graph<Number,Number> g2)
+    {
+        int n1 = g1.getVertexCount();
+        int n2 = g2.getVertexCount();
+
+        assertEquals(n1, n2);
+
+        assertEquals(g1.getEdgeCount(), g2.getEdgeCount());
+
+        List<Number> id1 = new ArrayList<Number>(g1.getVertices());
+        List<Number> id2 = new ArrayList<Number>(g2.getVertices());
+
+        for (int i = 0; i < n1; i++)
+        {
+            Number v1 = id1.get(i);
+            Number v2 = id2.get(i);
+            assertNotNull(v1);
+            assertNotNull(v2);
+            
+            checkSets(g1.getPredecessors(v1), g2.getPredecessors(v2), id1, id2);
+            checkSets(g1.getSuccessors(v1), g2.getSuccessors(v2), id1, id2);
+        }
+    }
+
+    private void checkSets(Collection<Number> s1, Collection<Number> s2, List<Number> id1, List<Number> id2)
+    {
+        for (Number u : s1)
+        {
+            int j = id1.indexOf(u);
+            assertTrue(s2.contains(id2.get(j)));
+        }
+    }
+
+    private class GreekLabels<V> implements Function<V,String>
+    {
+        private List<V> id; 
+        
+        public GreekLabels(List<V> id)
+        {
+            this.id = id;
+        }
+        
+        public String apply(V v)
+        {
+            return vertex_labels[id.indexOf(v)];
+        }
+        
+    }    
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/TestGraphMLReader.java b/jung-io/src/test/java/edu/uci/ics/jung/io/TestGraphMLReader.java
new file mode 100644
index 0000000..0d68695
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/TestGraphMLReader.java
@@ -0,0 +1,280 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.io;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import junit.framework.Assert;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.xml.sax.SAXException;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.common.collect.BiMap;
+
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.graph.SetHypergraph;
+import edu.uci.ics.jung.graph.UndirectedSparseGraph;
+
+/**
+ * @author Scott White
+ * @author Tom Nelson - converted to jung2
+ */
+public class TestGraphMLReader extends TestCase
+{
+
+    Supplier<Graph<Number, Number>> graphFactory;
+    Supplier<Number> vertexFactory;
+    Supplier<Number> edgeFactory;
+    GraphMLReader<Graph<Number, Number>, Number, Number> gmlreader;
+
+    public static Test suite()
+    {
+        return new TestSuite(TestGraphMLReader.class);
+    }
+
+    @Override
+    protected void setUp() throws ParserConfigurationException, SAXException
+    {
+        graphFactory = new Supplier<Graph<Number, Number>>()
+        {
+            public Graph<Number, Number> get()
+            {
+                return new DirectedSparseMultigraph<Number, Number>();
+            }
+        };
+        vertexFactory = new Supplier<Number>()
+        {
+            int n = 0;
+
+            public Number get()
+            {
+                return n++;
+            }
+        };
+        edgeFactory = new Supplier<Number>()
+        {
+            int n = 0;
+
+            public Number get()
+            {
+                return n++;
+            }
+        };
+        gmlreader = new GraphMLReader<Graph<Number, Number>, Number, Number>(
+                vertexFactory, edgeFactory);
+    }
+
+    public void testLoad() throws IOException
+    {
+        String testFilename = "toy_graph.ml";
+
+        Graph<Number, Number> graph = loadGraph(testFilename);
+
+        Assert.assertEquals(graph.getVertexCount(), 3);
+        Assert.assertEquals(graph.getEdgeCount(), 3);
+
+        BiMap<Number, String> vertex_ids = gmlreader.getVertexIDs();
+
+        Number joe = vertex_ids.inverse().get("1");
+        Number bob = vertex_ids.inverse().get("2");
+        Number sue = vertex_ids.inverse().get("3");
+
+        Assert.assertNotNull(joe);
+        Assert.assertNotNull(bob);
+        Assert.assertNotNull(sue);
+        
+        Map<String, GraphMLMetadata<Number>> vertex_metadata = 
+        	gmlreader.getVertexMetadata();
+        Function<Number, String> name = 
+        	vertex_metadata.get("name").transformer;
+        Assert.assertEquals(name.apply(joe), "Joe");
+        Assert.assertEquals(name.apply(bob), "Bob");
+        Assert.assertEquals(name.apply(sue), "Sue");
+
+        Assert.assertTrue(graph.isPredecessor(joe, bob));
+        Assert.assertTrue(graph.isPredecessor(bob, joe));
+        Assert.assertTrue(graph.isPredecessor(sue, joe));
+        Assert.assertFalse(graph.isPredecessor(joe, sue));
+        Assert.assertFalse(graph.isPredecessor(sue, bob));
+        Assert.assertFalse(graph.isPredecessor(bob, sue));
+
+        File testFile = new File(testFilename);
+        testFile.delete();
+    }
+
+    public void testAttributes() throws IOException
+    {
+        Graph<Number, Number> graph = new UndirectedSparseGraph<Number, Number>();
+        gmlreader.load("src/test/resources/edu/uci/ics/jung/io/graphml/attributes.graphml", graph);
+
+        Assert.assertEquals(graph.getVertexCount(), 6);
+        Assert.assertEquals(graph.getEdgeCount(), 7);
+
+        // test vertex IDs
+        BiMap<Number, String> vertex_ids = gmlreader.getVertexIDs();
+        for (Map.Entry<Number, String> entry : vertex_ids.entrySet())
+        {
+            Assert.assertEquals(entry.getValue().charAt(0), 'n');
+            Assert.assertEquals(
+                    Integer.parseInt(entry.getValue().substring(1)), entry
+                            .getKey().intValue());
+        }
+
+        // test edge IDs
+        BiMap<Number, String> edge_ids = gmlreader.getEdgeIDs();
+        for (Map.Entry<Number, String> entry : edge_ids.entrySet())
+        {
+            Assert.assertEquals(entry.getValue().charAt(0), 'e');
+            Assert.assertEquals(
+                    Integer.parseInt(entry.getValue().substring(1)), entry
+                            .getKey().intValue());
+        }
+
+        // test data
+//        Map<String, SettableTransformer<Number, String>> vertex_data = gmlreader
+//                .getVertexData();
+//        Map<String, SettableTransformer<Number, String>> edge_data = gmlreader
+//                .getEdgeData();
+        Map<String, GraphMLMetadata<Number>> vertex_metadata = 
+        	gmlreader.getVertexMetadata();
+        Map<String, GraphMLMetadata<Number>> edge_metadata = 
+        	gmlreader.getEdgeMetadata();
+        
+
+        // test vertex colors
+//        Transformer<Number, String> vertex_color = vertex_data.get("d0");
+        Function<Number, String> vertex_color = 
+        	vertex_metadata.get("d0").transformer;
+        Assert.assertEquals(vertex_color.apply(0), "green");
+        Assert.assertEquals(vertex_color.apply(1), "yellow");
+        Assert.assertEquals(vertex_color.apply(2), "blue");
+        Assert.assertEquals(vertex_color.apply(3), "red");
+        Assert.assertEquals(vertex_color.apply(4), "yellow");
+        Assert.assertEquals(vertex_color.apply(5), "turquoise");
+
+        // test edge weights
+//        Transformer<Number, String> edge_weight = edge_data.get("d1");
+        Function<Number, String> edge_weight = 
+        	edge_metadata.get("d1").transformer;
+        Assert.assertEquals(edge_weight.apply(0), "1.0");
+        Assert.assertEquals(edge_weight.apply(1), "1.0");
+        Assert.assertEquals(edge_weight.apply(2), "2.0");
+        Assert.assertEquals(edge_weight.apply(3), null);
+        Assert.assertEquals(edge_weight.apply(4), null);
+        Assert.assertEquals(edge_weight.apply(5), null);
+        Assert.assertEquals(edge_weight.apply(6), "1.1");
+
+    }
+
+    public void testLoadHypergraph() throws IOException,
+            ParserConfigurationException, SAXException
+    {
+        Hypergraph<Number, Number> graph = new SetHypergraph<Number, Number>();
+        GraphMLReader<Hypergraph<Number, Number>, Number, Number> hyperreader = 
+            new GraphMLReader<Hypergraph<Number, Number>, Number, Number>(
+                vertexFactory, edgeFactory);
+        hyperreader.load("src/test/resources/edu/uci/ics/jung/io/graphml/hyper.graphml", graph);
+
+        Assert.assertEquals(graph.getVertexCount(), 7);
+        Assert.assertEquals(graph.getEdgeCount(), 4);
+
+        // n0
+        Set<Number> incident = new HashSet<Number>();
+        incident.add(0);
+        incident.add(3);
+        Assert.assertEquals(graph.getIncidentEdges(0), incident);
+
+        // n1
+        incident.clear();
+        incident.add(0);
+        incident.add(2);
+        Assert.assertEquals(graph.getIncidentEdges(1), incident);
+
+        // n2
+        incident.clear();
+        incident.add(0);
+        Assert.assertEquals(graph.getIncidentEdges(2), incident);
+
+        // n3
+        incident.clear();
+        incident.add(1);
+        incident.add(2);
+        Assert.assertEquals(graph.getIncidentEdges(3), incident);
+
+        // n4
+        incident.clear();
+        incident.add(1);
+        incident.add(3);
+        Assert.assertEquals(graph.getIncidentEdges(4), incident);
+
+        // n5
+        incident.clear();
+        incident.add(1);
+        Assert.assertEquals(graph.getIncidentEdges(5), incident);
+
+        // n6
+        incident.clear();
+        incident.add(1);
+        Assert.assertEquals(graph.getIncidentEdges(6), incident);
+    }
+
+    private Graph<Number, Number> loadGraph(String testFilename)
+            throws IOException
+    {
+        BufferedWriter writer = new BufferedWriter(new FileWriter(
+                testFilename));
+        writer.write("<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>\n");
+        writer.write("<?meta name=\"GENERATOR\" content=\"XML::Smart 1.3.1\" ?>\n");
+        writer.write("<graph edgedefault=\"directed\">\n");
+        writer.write("<node id=\"1\" name=\"Joe\"/>\n");
+        writer.write("<node id=\"2\" name=\"Bob\"/>\n");
+        writer.write("<node id=\"3\" name=\"Sue\"/>\n");
+        writer.write("<edge source=\"1\" target=\"2\"/>\n");
+        writer.write("<edge source=\"2\" target=\"1\"/>\n");
+        writer.write("<edge source=\"1\" target=\"3\"/>\n");
+        writer.write("</graph>\n");
+        writer.close();
+
+        Graph<Number, Number> graph = graphFactory.get();
+        gmlreader.load(testFilename, graph);
+        return graph;
+    }
+
+//    public void testSave() {
+//        String testFilename = "toy_graph.ml";
+//        Graph<Number,Number> oldGraph = loadGraph(testFilename);
+////        GraphMLFile<Number,Number> graphmlFile = new GraphMLFile();
+//        String newFilename = testFilename + "_save";
+//        gmlreader.save(oldGraph,newFilename);
+//		Graph<Number,Number> newGraph = gmlreader.load(newFilename);
+//        Assert.assertEquals(oldGraph.getVertexCount(),newGraph.getVertexCount());
+//        Assert.assertEquals(oldGraph.getEdgeCount(),newGraph.getEdgeCount());
+//        File testFile = new File(testFilename);
+//        testFile.delete();
+//        File newFile = new File(newFilename);
+//        newFile.delete();
+//
+//
+//    }
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/TestGraphMLWriter.java b/jung-io/src/test/java/edu/uci/ics/jung/io/TestGraphMLWriter.java
new file mode 100644
index 0000000..a9feca2
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/TestGraphMLWriter.java
@@ -0,0 +1,169 @@
+/*
+ * Created on Jun 22, 2008
+ *
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.io;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import org.xml.sax.SAXException;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.graph.DirectedSparseGraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+
+public class TestGraphMLWriter extends TestCase
+{
+    public void testBasicWrite() throws IOException, ParserConfigurationException, SAXException
+    {
+        Graph<String, Number> g = TestGraphs.createTestGraph(true);
+        GraphMLWriter<String, Number> gmlw = new GraphMLWriter<String, Number>();
+        Function<Number, String> edge_weight = new Function<Number, String>() 
+		{ 
+			public String apply(Number n) 
+			{ 
+				return String.valueOf(n.intValue()); 
+			} 
+		};
+
+		Function<String, String> vertex_name = Functions.identity();
+			//TransformerUtils.nopTransformer();
+		
+        gmlw.addEdgeData("weight", "integer value for the edge", 
+        		Integer.toString(-1), edge_weight);
+        gmlw.addVertexData("name", "identifier for the vertex", null, vertex_name);
+        gmlw.setEdgeIDs(edge_weight);
+        gmlw.setVertexIDs(vertex_name);
+        gmlw.save(g, new FileWriter("src/test/resources/testbasicwrite.graphml"));
+        
+        // TODO: now read it back in and compare the graph connectivity 
+        // and other metadata with what's in TestGraphs.pairs[], etc.
+//        Factory<String> vertex_factory = null;
+//        Factory<Object> edge_factory = FactoryUtils.instantiateFactory(Object.class);
+//        GraphMLReader<Graph<String, Object>, String, Object> gmlr = 
+//        	new GraphMLReader<Graph<String, Object>, String, Object>(
+//        			vertex_factory, edge_factory);
+        GraphMLReader<Graph<String, Object>, String, Object> gmlr = 
+            new GraphMLReader<Graph<String, Object>, String, Object>();
+        Graph<String, Object> g2 = new DirectedSparseGraph<String, Object>();
+        gmlr.load("src/test/resources/testbasicwrite.graphml", g2);
+        Map<String, GraphMLMetadata<Object>> edge_metadata = 
+        	gmlr.getEdgeMetadata();
+        Function<Object, String> edge_weight2 = 
+        	edge_metadata.get("weight").transformer;
+        validateTopology(g, g2, edge_weight, edge_weight2);
+        
+        // TODO: delete graph file when done
+        File f = new File("src/test/resources/testbasicwrite.graphml");
+        f.delete();
+    }
+    
+    public void testMixedGraph() throws IOException, ParserConfigurationException, SAXException
+    {
+        Graph<String, Number> g = TestGraphs.getSmallGraph();
+        GraphMLWriter<String, Number> gmlw = new GraphMLWriter<String, Number>();
+        Function<Number, String> edge_weight = new Function<Number, String>() 
+        { 
+            public String apply(Number n) 
+            { 
+                return String.valueOf(n.doubleValue()); 
+            } 
+        };
+
+        gmlw.addEdgeData("weight", "integer value for the edge", 
+                Integer.toString(-1), edge_weight);
+        gmlw.setEdgeIDs(edge_weight);
+        gmlw.save(g, new FileWriter("src/test/resources/testmixedgraph.graphml"));
+
+        // TODO: now read it back in and compare the graph connectivity 
+        // and other metadata with what's in TestGraphs, etc.
+        GraphMLReader<Graph<String,Object>,String,Object> gmlr = 
+            new GraphMLReader<Graph<String,Object>,String,Object>();
+        Graph<String,Object> g2 = new SparseMultigraph<String,Object>();
+        gmlr.load("src/test/resources/testmixedgraph.graphml", g2);
+        Map<String, GraphMLMetadata<Object>> edge_metadata = 
+            gmlr.getEdgeMetadata();
+        Function<Object, String> edge_weight2 = 
+            edge_metadata.get("weight").transformer;
+        validateTopology(g, g2, edge_weight, edge_weight2);
+        
+        // TODO: delete graph file when done
+        File f = new File("src/test/resources/testmixedgraph.graphml");
+        f.delete();
+    }
+
+    public <T extends Comparable<T>> void validateTopology(Graph<T,Number> g, Graph<T,Object> g2,
+            Function<Number,String> edge_weight, Function<Object,String> edge_weight2)
+    {
+        Assert.assertEquals(g2.getEdgeCount(), g.getEdgeCount());
+        List<T> g_vertices = new ArrayList<T>(g.getVertices());
+        List<T> g2_vertices = new ArrayList<T>(g2.getVertices());
+        Collections.sort(g_vertices); 
+        Collections.sort(g2_vertices);
+        Assert.assertEquals(g_vertices, g2_vertices);
+
+        Set<String> g_edges = new HashSet<String>();
+        for (Number n : g.getEdges())
+            g_edges.add(String.valueOf(n));
+        Set<Object> g2_edges = new HashSet<Object>(g2.getEdges());
+        Assert.assertEquals(g_edges, g2_edges);
+        
+        for (T v : g2.getVertices())
+        {
+            for (T w : g2.getVertices())
+            {
+                Assert.assertEquals(g.isNeighbor(v, w), 
+                        g2.isNeighbor(v, w));
+                Set<String> e = new HashSet<String>();
+                for (Number n : g.findEdgeSet(v, w))
+                    e.add(String.valueOf(n));
+                Set<Object> e2 = new HashSet<Object>(g2.findEdgeSet(v, w));
+                Assert.assertEquals(e.size(), e2.size());
+                Assert.assertEquals(e, e2);
+            }
+        }
+        
+        for (Object o : g2.getEdges())
+        {
+            String weight = edge_weight.apply(new Double((String)o));
+            String weight2 = edge_weight2.apply(o);
+            Assert.assertEquals(weight2, weight);
+        }        
+//                Number n = g.findEdge(v, w);
+//                Object o = g2.findEdge(v, w);
+//                if (n != null)
+//                {
+//                    String weight = edge_weight.apply(n);
+//                    String weight2 = edge_weight2.apply(o);
+//                    Assert.assertEquals(weight2, weight);
+//                }
+//            }
+//        }
+        
+    }
+    
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/DummyEdge.java b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/DummyEdge.java
new file mode 100644
index 0000000..3582412
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/DummyEdge.java
@@ -0,0 +1,29 @@
+package edu.uci.ics.jung.io.graphml;
+
+import com.google.common.base.Function;
+
+public class DummyEdge extends DummyGraphObjectBase {
+    
+    public static class EdgeFactory implements Function<EdgeMetadata, DummyEdge> {
+        int n = 100;
+
+        public DummyEdge apply(EdgeMetadata md) {
+            return new DummyEdge(n++);
+        }
+    }
+    
+    public static class HyperEdgeFactory implements Function<HyperEdgeMetadata, DummyEdge> {
+        int n = 0;
+
+        public DummyEdge apply(HyperEdgeMetadata md) {
+            return new DummyEdge(n++);
+        }
+    }
+    
+    public DummyEdge() {
+    }
+
+    public DummyEdge(int v) {
+        super(v);
+    }
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/DummyGraphObjectBase.java b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/DummyGraphObjectBase.java
new file mode 100644
index 0000000..2df652d
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/DummyGraphObjectBase.java
@@ -0,0 +1,53 @@
+package edu.uci.ics.jung.io.graphml;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.graph.SetHypergraph;
+import edu.uci.ics.jung.graph.UndirectedSparseGraph;
+
+public class DummyGraphObjectBase {
+    
+    public static class UndirectedSparseGraphFactory implements Function<GraphMetadata, Hypergraph<DummyVertex, DummyEdge>> {
+    
+        public Hypergraph<DummyVertex, DummyEdge> apply(GraphMetadata arg0) {
+            return new UndirectedSparseGraph<DummyVertex, DummyEdge>();
+        }
+    }
+    
+    public static class SetHypergraphFactory implements Function<GraphMetadata, Hypergraph<DummyVertex, DummyEdge>> {
+        
+        public Hypergraph<DummyVertex, DummyEdge> apply(GraphMetadata arg0) {
+            return new SetHypergraph<DummyVertex, DummyEdge>();
+        }
+    }
+
+    public int myValue;
+
+    public DummyGraphObjectBase() {
+    }
+
+    public DummyGraphObjectBase(int v) {
+        myValue = v;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + myValue;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        DummyGraphObjectBase other = (DummyGraphObjectBase) obj;
+        return myValue == other.myValue;
+    }
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/DummyVertex.java b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/DummyVertex.java
new file mode 100644
index 0000000..22057df
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/DummyVertex.java
@@ -0,0 +1,21 @@
+package edu.uci.ics.jung.io.graphml;
+
+import com.google.common.base.Function;
+
+public class DummyVertex extends DummyGraphObjectBase {
+    
+    public static class Factory implements Function<NodeMetadata, DummyVertex> {
+        int n = 0;
+
+        public DummyVertex apply(NodeMetadata md) {
+            return new DummyVertex(n++);
+        }
+    }
+    
+    public DummyVertex() {
+    }
+
+    public DummyVertex(int v) {
+        super(v);
+    }
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/TestGraphMLReader2.java b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/TestGraphMLReader2.java
new file mode 100644
index 0000000..be92be1
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/TestGraphMLReader2.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.graph.SetHypergraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.io.GraphIOException;
+
+public class TestGraphMLReader2 {
+    static final String graphMLDocStart = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+            + "<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+            + "xsi:schemaLocation=\"http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd\">";
+
+    private GraphMLReader2<Hypergraph<DummyVertex, DummyEdge>, DummyVertex, DummyEdge> reader;
+
+    @After
+    public void tearDown() throws Exception {
+        if (reader != null) {
+            reader.close();
+        }
+        reader = null;
+    }
+
+
+    @Test(expected = GraphIOException.class)
+    public void testEmptyFile() throws Exception {
+
+        String xml = "";
+        readGraph(xml, new DummyGraphObjectBase.UndirectedSparseGraphFactory(),
+                new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory());
+    }
+
+    @Test
+    public void testBasics() throws Exception {
+
+        String xml = graphMLDocStart
+                + "<key id=\"d0\" for=\"node\" attr.name=\"color\" attr.type=\"string\">"
+                + "<default>yellow</default>"
+                + "</key>"
+                + "<key id=\"d1\" for=\"edge\" attr.name=\"weight\" attr.type=\"double\"/>"
+                + "<graph id=\"G\" edgedefault=\"undirected\">"
+                + "<node id=\"n0\">" + "<data key=\"d0\">green</data>"
+                + "</node>" + "<node id=\"n1\"/>" + "<node id=\"n2\">"
+                + "<data key=\"d0\">blue</data>" + "</node>"
+                + "<edge id=\"e0\" source=\"n0\" target=\"n2\">"
+                + "<data key=\"d1\">1.0</data>" + "</edge>" + "</graph>" + "</graphml>";
+
+        // Read the graph object.
+        Hypergraph<DummyVertex, DummyEdge> graph = readGraph(xml, new DummyGraphObjectBase.UndirectedSparseGraphFactory(),
+                new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory());
+
+        // Check out the graph.
+        Assert.assertNotNull(graph);
+        Assert.assertEquals(3, graph.getVertexCount());
+        Assert.assertEquals(1, graph.getEdgeCount());
+        Assert.assertEquals(0, graph.getEdgeCount(EdgeType.DIRECTED));
+        Assert.assertEquals(1, graph.getEdgeCount(EdgeType.UNDIRECTED));
+
+        // Check out metadata.
+        Assert.assertEquals(1, reader.getGraphMLDocument().getGraphMetadata().size());
+        List<EdgeMetadata> edges = new ArrayList<EdgeMetadata>(reader.getGraphMLDocument().getGraphMetadata().get(0).getEdgeMap().values());
+        Assert.assertEquals(1, edges.size());
+        Assert.assertEquals("n0", edges.get(0).getSource());
+        Assert.assertEquals("n2", edges.get(0).getTarget());
+    }
+
+    @Test
+    public void testData() throws Exception {
+
+        String xml =
+                graphMLDocStart +
+                        "<key id=\"d0\" for=\"node\" attr.name=\"color\" attr.type=\"string\">" +
+                        "<default>yellow</default>" +
+                        "</key>" +
+                        "<key id=\"d1\" for=\"edge\" attr.name=\"weight\" attr.type=\"double\"/>" +
+                        "<graph id=\"G\" edgedefault=\"undirected\">" +
+                        "<node id=\"n0\">" +
+                        "<data key=\"d0\">green</data>" +
+                        "</node>" +
+                        "<node id=\"n1\"/>" +
+                        "<node id=\"n2\">" +
+                        "<data key=\"d0\">blue</data>" +
+                        "</node>" +
+                        "<edge id=\"e0\" source=\"n0\" target=\"n2\">" +
+                        "<data key=\"d1\">1.0</data>" +
+                        "</edge>" +
+                        "</graph>" +
+                        "</graphml>";
+
+        // Read the graph object.
+        readGraph(xml, new DummyGraphObjectBase.UndirectedSparseGraphFactory(),
+                new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory());
+
+        // Check out metadata.
+        Assert.assertEquals(1, reader.getGraphMLDocument().getGraphMetadata().size());
+        List<EdgeMetadata> edges = new ArrayList<EdgeMetadata>(reader.getGraphMLDocument().getGraphMetadata().get(0).getEdgeMap().values());
+        List<NodeMetadata> nodes = new ArrayList<NodeMetadata>(reader.getGraphMLDocument().getGraphMetadata().get(0).getNodeMap().values());
+        Collections.sort(nodes, new Comparator<NodeMetadata>() {
+            public int compare(NodeMetadata o1, NodeMetadata o2) {
+                return o1.getId().compareTo(o2.getId());
+            }            
+        });
+        Assert.assertEquals(1, edges.size());
+        Assert.assertEquals("1.0", edges.get(0).getProperties().get("d1"));
+        Assert.assertEquals(3, nodes.size());
+        Assert.assertEquals("green", nodes.get(0).getProperties().get("d0"));
+        Assert.assertEquals("yellow", nodes.get(1).getProperties().get("d0"));
+        Assert.assertEquals("blue", nodes.get(2).getProperties().get("d0"));
+    }
+
+    @Test(expected = GraphIOException.class)
+    public void testEdgeWithInvalidNode() throws Exception {
+
+        String xml = graphMLDocStart
+                + "<key id=\"d0\" for=\"node\" attr.name=\"color\" attr.type=\"string\">"
+                + "<default>yellow</default>"
+                + "</key>"
+                + "<key id=\"d1\" for=\"edge\" attr.name=\"weight\" attr.type=\"double\"/>"
+                + "<graph id=\"G\" edgedefault=\"undirected\">"
+                + "<node id=\"n0\">" + "<data key=\"d0\">green</data>"
+                + "</node>" + "<node id=\"n1\"/>" + "<node id=\"n2\">"
+                + "<data key=\"d0\">blue</data>" + "</node>"
+                + "<edge id=\"e0\" source=\"n0\" target=\"n3\">" + // Invalid
+                // node: n3
+                "<data key=\"d1\">1.0</data>" + "</edge>" + "</graphml>";
+
+        readGraph(xml, new DummyGraphObjectBase.UndirectedSparseGraphFactory(), new DummyVertex.Factory(),
+                new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory());
+    }
+
+    @Test
+    public void testHypergraph() throws Exception {
+
+        String xml = graphMLDocStart
+                + "<key id=\"d0\" for=\"node\" attr.name=\"color\" attr.type=\"string\">"
+                + "<default>yellow</default>"
+                + "</key>"
+                + "<key id=\"d1\" for=\"edge\" attr.name=\"weight\" attr.type=\"double\"/>"
+                + "<graph id=\"G\" edgedefault=\"undirected\">"
+                + "<node id=\"n0\">" + "<data key=\"d0\">green</data>"
+                + "</node>" + "<node id=\"n1\"/>" + "<node id=\"n2\">"
+                + "<data key=\"d0\">blue</data>" + "</node>"
+                + "<hyperedge id=\"e0\">"
+                + "<endpoint node=\"n0\"/>" + "<endpoint node=\"n1\"/>"
+                + "<endpoint node=\"n2\"/>" + "</hyperedge>" + "</graph>" + "</graphml>";
+
+        // Read the graph object.
+        Hypergraph<DummyVertex, DummyEdge> graph = readGraph(xml, new DummyGraphObjectBase.SetHypergraphFactory(),
+                new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory());
+
+        // Check out the graph.
+        Assert.assertNotNull(graph);
+        Assert.assertEquals(3, graph.getVertexCount());
+        Assert.assertEquals(1, graph.getEdgeCount());
+        Assert.assertEquals(0, graph.getEdgeCount(EdgeType.DIRECTED));
+        Assert.assertEquals(1, graph.getEdgeCount(EdgeType.UNDIRECTED));
+
+        // Check out metadata.
+        Assert.assertEquals(1, reader.getGraphMLDocument().getGraphMetadata().size());
+        List<HyperEdgeMetadata> edges = new ArrayList<HyperEdgeMetadata>(reader.getGraphMLDocument().getGraphMetadata().get(0).getHyperEdgeMap().values());
+        Assert.assertEquals(1, edges.size());
+        Assert.assertEquals(3, edges.get(0).getEndpoints().size());
+        Assert.assertEquals("n0", edges.get(0).getEndpoints().get(0).getNode());
+        Assert.assertEquals("n1", edges.get(0).getEndpoints().get(1).getNode());
+        Assert.assertEquals("n2", edges.get(0).getEndpoints().get(2).getNode());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidGraphFactory() throws Exception {
+
+        // Need a hypergraph
+        String xml = graphMLDocStart
+                + "<key id=\"d0\" for=\"node\" attr.name=\"color\" attr.type=\"string\">"
+                + "<default>yellow</default>"
+                + "</key>"
+                + "<key id=\"d1\" for=\"edge\" attr.name=\"weight\" attr.type=\"double\"/>"
+                + "<graph id=\"G\" edgedefault=\"undirected\">"
+                + "<node id=\"n0\">" + "<data key=\"d0\">green</data>"
+                + "</node>" + "<node id=\"n1\"/>" + "<node id=\"n2\">"
+                + "<data key=\"d0\">blue</data>" + "</node>"
+                + "<hyperedge id=\"e0\">"
+                + "<endpoint node=\"n0\"/>" + "<endpoint node=\"n1\"/>"
+                + "<endpoint node=\"n2\"/>" + "</hyperedge>" + "</graphml>";
+
+	// This will attempt to add an edge with an invalid number of incident vertices (3)
+	// for an UndirectedGraph, which should trigger an IllegalArgumentException.
+        readGraph(xml, new DummyGraphObjectBase.UndirectedSparseGraphFactory(),
+                new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory());
+    }
+
+    @Test
+    public void testAttributesFile() throws Exception {
+
+        // Read the graph object.
+        Hypergraph<DummyVertex, DummyEdge> graph = readGraphFromFile("attributes.graphml", new DummyGraphObjectBase.UndirectedSparseGraphFactory(),
+                new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory());
+
+        Assert.assertEquals(6, graph.getVertexCount());
+        Assert.assertEquals(7, graph.getEdgeCount());
+
+        Assert.assertEquals(1, reader.getGraphMLDocument().getGraphMetadata().size());
+
+        // Test node ids
+        int id = 0;        
+        List<NodeMetadata> nodes = new ArrayList<NodeMetadata>(reader.getGraphMLDocument().getGraphMetadata().get(0).getNodeMap().values());
+        Collections.sort(nodes, new Comparator<NodeMetadata>() {
+            public int compare(NodeMetadata o1, NodeMetadata o2) {
+                return o1.getId().compareTo(o2.getId());
+            }            
+        });
+        Assert.assertEquals(6, nodes.size());
+        for (NodeMetadata md : nodes) {
+            Assert.assertEquals('n', md.getId().charAt(0));
+            Assert.assertEquals(id++, Integer.parseInt(md.getId().substring(1)));
+        }
+
+        // Test edge ids
+        id = 0;
+        List<EdgeMetadata> edges = new ArrayList<EdgeMetadata>(reader.getGraphMLDocument().getGraphMetadata().get(0).getEdgeMap().values());
+        Collections.sort(edges, new Comparator<EdgeMetadata>() {
+            public int compare(EdgeMetadata o1, EdgeMetadata o2) {
+                return o1.getId().compareTo(o2.getId());
+            }            
+        });
+        Assert.assertEquals(7, edges.size());
+        for (EdgeMetadata md : edges) {
+            Assert.assertEquals('e', md.getId().charAt(0));
+            Assert.assertEquals(id++, Integer.parseInt(md.getId().substring(1)));
+        }
+
+        Assert.assertEquals("green", nodes.get(0).getProperties().get("d0"));
+        Assert.assertEquals("yellow", nodes.get(1).getProperties().get("d0"));
+        Assert.assertEquals("blue", nodes.get(2).getProperties().get("d0"));
+        Assert.assertEquals("red", nodes.get(3).getProperties().get("d0"));
+        Assert.assertEquals("yellow", nodes.get(4).getProperties().get("d0"));
+        Assert.assertEquals("turquoise", nodes.get(5).getProperties().get("d0"));
+
+        Assert.assertEquals("1.0", edges.get(0).getProperties().get("d1"));
+        Assert.assertEquals("1.0", edges.get(1).getProperties().get("d1"));
+        Assert.assertEquals("2.0", edges.get(2).getProperties().get("d1"));
+        Assert.assertEquals(null, edges.get(3).getProperties().get("d1"));
+        Assert.assertEquals(null, edges.get(4).getProperties().get("d1"));
+        Assert.assertEquals(null, edges.get(5).getProperties().get("d1"));
+        Assert.assertEquals("1.1", edges.get(6).getProperties().get("d1"));
+    }
+
+    @Test
+    public void testHypergraphFile() throws Exception {
+
+        Function<GraphMetadata, Hypergraph<Number, Number>> graphFactory = new Function<GraphMetadata, Hypergraph<Number, Number>>() {
+            public Hypergraph<Number, Number> apply(GraphMetadata md) {
+                return new SetHypergraph<Number, Number>();
+            }
+        };
+
+        Function<NodeMetadata, Number> vertexFactory = new Function<NodeMetadata, Number>() {
+            int n = 0;
+
+            public Number apply(NodeMetadata md) {
+                return n++;
+            }
+        };
+
+        Function<EdgeMetadata, Number> edgeFactory = new Function<EdgeMetadata, Number>() {
+            int n = 100;
+
+            public Number apply(EdgeMetadata md) {
+                return n++;
+            }
+        };
+
+        Function<HyperEdgeMetadata, Number> hyperEdgeFactory = new Function<HyperEdgeMetadata, Number>() {
+            int n = 0;
+
+            public Number apply(HyperEdgeMetadata md) {
+                return n++;
+            }
+        };
+
+        // Read the graph object.        
+        Reader fileReader = new InputStreamReader(getClass().getResourceAsStream("hyper.graphml"));
+        GraphMLReader2<Hypergraph<Number, Number>, Number, Number> hyperreader =
+                new GraphMLReader2<Hypergraph<Number, Number>, Number, Number>(fileReader,
+                        graphFactory, vertexFactory, edgeFactory, hyperEdgeFactory);
+
+        // Read the graph.
+        Hypergraph<Number, Number> graph = hyperreader.readGraph();
+
+        Assert.assertEquals(graph.getVertexCount(), 7);
+        Assert.assertEquals(graph.getEdgeCount(), 4);
+
+        // n0
+        Set<Number> incident = new HashSet<Number>();
+        incident.add(0);
+        incident.add(100);
+        Assert.assertEquals(incident, graph.getIncidentEdges(0));
+
+        // n1
+        incident.clear();
+        incident.add(0);
+        incident.add(2);
+        Assert.assertEquals(incident, graph.getIncidentEdges(1));
+
+        // n2
+        incident.clear();
+        incident.add(0);
+        Assert.assertEquals(incident, graph.getIncidentEdges(2));
+
+        // n3
+        incident.clear();
+        incident.add(1);
+        incident.add(2);
+        Assert.assertEquals(incident, graph.getIncidentEdges(3));
+
+        // n4
+        incident.clear();
+        incident.add(1);
+        incident.add(100);
+        Assert.assertEquals(incident, graph.getIncidentEdges(4));
+
+        // n5
+        incident.clear();
+        incident.add(1);
+        Assert.assertEquals(incident, graph.getIncidentEdges(5));
+
+        // n6
+        incident.clear();
+        incident.add(1);
+        Assert.assertEquals(incident, graph.getIncidentEdges(6));
+    }
+
+    /*@Test
+    public void testReader1Perf() throws Exception {
+        String fileName = "attributes.graphml";                
+        
+        long totalTime = 0;
+        int numTrials = 1000;
+
+        for( int ix=0; ix<numTrials; ++ix ) {
+            Reader fileReader = new InputStreamReader(getClass().getResourceAsStream(fileName));
+                        
+            GraphMLReader<Hypergraph<DummyVertex, DummyEdge>, DummyVertex, DummyEdge> reader = new GraphMLReader<Hypergraph<DummyVertex, DummyEdge>, DummyVertex, DummyEdge>(new Factory<DummyVertex>() {
+
+                public DummyVertex create() {
+                    return new DummyVertex();
+                }
+                
+            }, new Factory<DummyEdge>() {
+                public DummyEdge create() {
+                    return new DummyEdge();
+                }
+            });   
+            
+            Thread.sleep(10);
+            
+            long start = System.currentTimeMillis();
+            Hypergraph<DummyVertex, DummyEdge> graph = new UndirectedSparseGraph<DummyVertex, DummyEdge>();
+            reader.load(fileReader, graph);
+            long duration = System.currentTimeMillis() - start;
+            totalTime += duration;
+        }
+        
+        double avgTime = ((double)totalTime / (double)numTrials) / 1000.0; 
+        
+        System.out.printf("Reader1: totalTime=%6d, numTrials=%6d, avgTime=%2.6f seconds", totalTime, numTrials, avgTime);
+        System.out.println();
+    }
+
+    @Test
+    public void testReader2Perf() throws Exception {
+        String fileName = "attributes.graphml";                
+        
+        long totalTime = 0;
+        int numTrials = 1000;
+
+        // Test reader2
+        for( int ix=0; ix<numTrials; ++ix ) {
+            Reader fileReader = new InputStreamReader(getClass().getResourceAsStream(fileName));       
+            reader = new GraphMLReader2<Hypergraph<DummyVertex, DummyEdge>, DummyVertex, DummyEdge>(
+                    fileReader, new DummyGraphObjectBase.UndirectedSparseGraphFactory(),
+                    new DummyVertex.Factory(), new DummyEdge.EdgeFactory(), new DummyEdge.HyperEdgeFactory());
+            reader.init();
+            
+            Thread.sleep(10);
+            
+            long start = System.currentTimeMillis();
+            reader.readGraph();
+            long duration = System.currentTimeMillis() - start;
+            totalTime += duration;
+            
+            reader.close();
+        }
+        
+        double avgTime = ((double)totalTime / (double)numTrials) / 1000.0; 
+        
+        System.out.printf("Reader2: totalTime=%6d, numTrials=%6d, avgTime=%2.6f seconds", totalTime, numTrials, avgTime);
+        System.out.println();
+    }*/
+
+    private Hypergraph<DummyVertex, DummyEdge> readGraph(String xml, Function<GraphMetadata, Hypergraph<DummyVertex, DummyEdge>> gf,
+                                                 DummyVertex.Factory nf, DummyEdge.EdgeFactory ef, DummyEdge.HyperEdgeFactory hef)
+            throws GraphIOException {
+        Reader fileReader = new StringReader(xml);
+        reader = new GraphMLReader2<Hypergraph<DummyVertex, DummyEdge>, DummyVertex, DummyEdge>(
+                fileReader, gf, nf, ef, hef);
+
+        return reader.readGraph();
+    }
+
+    private Hypergraph<DummyVertex, DummyEdge> readGraphFromFile(String file, Function<GraphMetadata, Hypergraph<DummyVertex, DummyEdge>> gf,
+            DummyVertex.Factory nf, DummyEdge.EdgeFactory ef, DummyEdge.HyperEdgeFactory hef)
+            throws Exception {
+        InputStream is = getClass().getResourceAsStream(file);
+        Reader fileReader = new InputStreamReader(is);
+        reader = new GraphMLReader2<Hypergraph<DummyVertex, DummyEdge>, DummyVertex, DummyEdge>(
+                fileReader, gf, nf, ef, hef);
+
+        return reader.readGraph();
+    }
+
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/AbstractParserTest.java b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/AbstractParserTest.java
new file mode 100644
index 0000000..6eb7818
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/AbstractParserTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import java.io.Reader;
+import java.io.StringReader;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.After;
+
+import edu.uci.ics.jung.graph.Hypergraph;
+import edu.uci.ics.jung.io.graphml.DummyEdge;
+import edu.uci.ics.jung.io.graphml.DummyGraphObjectBase;
+import edu.uci.ics.jung.io.graphml.KeyMap;
+import edu.uci.ics.jung.io.graphml.DummyVertex;
+
+public abstract class AbstractParserTest {
+
+    
+    private ElementParserRegistry<Hypergraph<DummyVertex,DummyEdge>,DummyVertex,DummyEdge> registry;
+
+    @Before
+    public void setUp() throws Exception {
+        registry = new ElementParserRegistry<Hypergraph<DummyVertex,DummyEdge>,DummyVertex,DummyEdge>(
+                new KeyMap(), 
+                new DummyGraphObjectBase.UndirectedSparseGraphFactory(), 
+                new DummyVertex.Factory(), 
+                new DummyEdge.EdgeFactory(), 
+                new DummyEdge.HyperEdgeFactory());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        registry = null;
+    }
+    
+    protected Object readObject(String xml) throws Exception {
+
+        Reader fileReader = new StringReader(xml);
+        XMLInputFactory factory = XMLInputFactory.newInstance();
+        XMLEventReader xmlEventReader = factory.createXMLEventReader(fileReader);
+        xmlEventReader = factory.createFilteredReader(xmlEventReader,
+                new GraphMLEventFilter());
+        
+        try {        
+            while( xmlEventReader.hasNext() ) {
+                XMLEvent event = xmlEventReader.nextEvent();
+                if (event.isStartElement()) {
+                    
+                    StartElement start = event.asStartElement();
+                    String name = start.getName().getLocalPart();
+                    return registry.getParser(name).parse(xmlEventReader, start);
+                }
+            }
+        } finally {
+            xmlEventReader.close();
+        }
+        
+        Assert.fail("failed to read object from XML: " + xml);
+        return null;
+    }
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestEdgeElementParser.java b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestEdgeElementParser.java
new file mode 100644
index 0000000..810bc36
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestEdgeElementParser.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.EdgeMetadata;
+
+public class TestEdgeElementParser extends AbstractParserTest {
+
+    @Test(expected= GraphIOException.class)
+    public void testNoSource() throws Exception {
+        
+        String xml = 
+            "<edge target=\"2\"/>";
+        
+        readObject(xml);
+    }
+
+    @Test(expected= GraphIOException.class)
+    public void testNoTarget() throws Exception {
+        
+        String xml = 
+            "<edge source=\"2\"/>";
+        
+        readObject(xml);
+    }
+
+    @Test
+    public void testId() throws Exception {
+        
+        String xml = 
+            "<edge source=\"1\" target=\"2\" id=\"e1\"/>";
+        
+        EdgeMetadata edge = (EdgeMetadata) readObject(xml);
+        Assert.assertNotNull(edge);
+        Assert.assertEquals("e1", edge.getId());
+        Assert.assertEquals(null, edge.getDescription());
+        Assert.assertEquals("1", edge.getSource());
+        Assert.assertEquals("2", edge.getTarget());
+        Assert.assertEquals(null, edge.isDirected());
+        Assert.assertEquals(null, edge.getSourcePort());
+        Assert.assertEquals(null, edge.getTargetPort());
+    }
+
+    @Test
+    public void testDirectedTrue() throws Exception {
+        
+        String xml = 
+            "<edge source=\"1\" target=\"2\" directed=\"true\"/>";
+        
+        EdgeMetadata edge = (EdgeMetadata) readObject(xml);
+        Assert.assertNotNull(edge);
+        Assert.assertEquals(null, edge.getId());
+        Assert.assertEquals(null, edge.getDescription());
+        Assert.assertEquals("1", edge.getSource());
+        Assert.assertEquals("2", edge.getTarget());
+        Assert.assertEquals(true, edge.isDirected());
+        Assert.assertEquals(null, edge.getSourcePort());
+        Assert.assertEquals(null, edge.getTargetPort());
+    }
+
+    @Test
+    public void testDirectedFalse() throws Exception {
+        
+        String xml = 
+            "<edge source=\"1\" target=\"2\" directed=\"false\"/>";
+        
+        EdgeMetadata edge = (EdgeMetadata) readObject(xml);
+        Assert.assertNotNull(edge);
+        Assert.assertEquals(null, edge.getId());
+        Assert.assertEquals(null, edge.getDescription());
+        Assert.assertEquals("1", edge.getSource());
+        Assert.assertEquals("2", edge.getTarget());
+        Assert.assertEquals(false, edge.isDirected());
+        Assert.assertEquals(null, edge.getSourcePort());
+        Assert.assertEquals(null, edge.getTargetPort());
+    }
+
+    @Test
+    public void testSourceTargetPorts() throws Exception {
+        
+        String xml = 
+            "<edge source=\"1\" target=\"2\" sourceport=\"a\" targetport=\"b\"/>";
+        
+        EdgeMetadata edge = (EdgeMetadata) readObject(xml);
+        Assert.assertNotNull(edge);
+        Assert.assertEquals(null, edge.getId());
+        Assert.assertEquals(null, edge.getDescription());
+        Assert.assertEquals("1", edge.getSource());
+        Assert.assertEquals("2", edge.getTarget());
+        Assert.assertEquals(null, edge.isDirected());
+        Assert.assertEquals("a", edge.getSourcePort());
+        Assert.assertEquals("b", edge.getTargetPort());
+    }
+
+    @Test
+    public void testDesc() throws Exception {
+        
+        String xml = 
+            "<edge source=\"1\" target=\"2\">" +
+                "<desc>hello world</desc>" +
+            "</edge>";
+        
+        EdgeMetadata edge = (EdgeMetadata) readObject(xml);
+        Assert.assertNotNull(edge);
+        Assert.assertEquals(null, edge.getId());
+        Assert.assertEquals("hello world", edge.getDescription());
+        Assert.assertEquals("1", edge.getSource());
+        Assert.assertEquals("2", edge.getTarget());
+        Assert.assertEquals(null, edge.isDirected());
+        Assert.assertEquals(null, edge.getSourcePort());
+        Assert.assertEquals(null, edge.getTargetPort());
+    }
+
+    @Test
+    public void testUserAttributes() throws Exception {
+        
+        String xml = 
+            "<edge source=\"1\" target=\"2\" bob=\"abc123\">" +         
+            "</edge>";
+        
+        EdgeMetadata edge = (EdgeMetadata) readObject(xml);
+        Assert.assertNotNull(edge);
+        Assert.assertEquals(null, edge.getId());
+        Assert.assertEquals(null, edge.getDescription());
+        Assert.assertEquals("1", edge.getSource());
+        Assert.assertEquals("2", edge.getTarget());
+        Assert.assertEquals(null, edge.isDirected());
+        Assert.assertEquals(null, edge.getSourcePort());
+        Assert.assertEquals(null, edge.getTargetPort());
+        Assert.assertEquals(1, edge.getProperties().size());
+        Assert.assertEquals("abc123", edge.getProperty("bob"));
+    }
+
+    @Test
+    public void testData() throws Exception {
+        
+        String xml = 
+            "<edge source=\"1\" target=\"2\">" +
+                "<data key=\"d1\">value1</data>" +
+                "<data key=\"d2\">value2</data>" +
+            "</edge>";
+        
+        EdgeMetadata edge = (EdgeMetadata) readObject(xml);
+        Assert.assertNotNull(edge);
+        Assert.assertEquals(null, edge.getId());
+        Assert.assertEquals(null, edge.getDescription());
+        Assert.assertEquals("1", edge.getSource());
+        Assert.assertEquals("2", edge.getTarget());
+        Assert.assertEquals(null, edge.isDirected());
+        Assert.assertEquals(null, edge.getSourcePort());
+        Assert.assertEquals(null, edge.getTargetPort());
+        Assert.assertEquals(2, edge.getProperties().size());
+        Assert.assertEquals("value1", edge.getProperty("d1"));
+        Assert.assertEquals("value2", edge.getProperty("d2"));
+    }
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestEndpointElementParser.java b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestEndpointElementParser.java
new file mode 100644
index 0000000..fc990bb
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestEndpointElementParser.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.EndpointMetadata;
+import edu.uci.ics.jung.io.graphml.EndpointMetadata.EndpointType;
+
+public class TestEndpointElementParser extends AbstractParserTest {
+
+    @Test(expected= GraphIOException.class)
+    public void testNoNode() throws Exception {
+        
+        String xml = 
+            "<endpoint/>";
+        
+        readObject(xml);
+    }
+
+    @Test
+    public void testId() throws Exception {
+        
+        String xml = 
+            "<endpoint node=\"1\" id=\"ep1\"/>";
+        
+        EndpointMetadata ep = (EndpointMetadata) readObject(xml);
+        Assert.assertNotNull(ep);
+        Assert.assertEquals("1", ep.getNode());
+        Assert.assertEquals("ep1", ep.getId());
+        Assert.assertEquals(null, ep.getDescription());        
+        Assert.assertEquals(null, ep.getPort());
+        Assert.assertEquals(EndpointType.UNDIR, ep.getEndpointType());
+    }
+
+    @Test
+    public void testDesc() throws Exception {
+        
+        String xml = 
+            "<endpoint node=\"1\" id=\"ep1\">" +
+                "<desc>hello world</desc>" +
+            "</endpoint>";
+        
+        EndpointMetadata ep = (EndpointMetadata) readObject(xml);
+        Assert.assertNotNull(ep);
+        Assert.assertEquals("1", ep.getNode());
+        Assert.assertEquals("ep1", ep.getId());
+        Assert.assertEquals("hello world", ep.getDescription());        
+        Assert.assertEquals(null, ep.getPort());
+        Assert.assertEquals(EndpointType.UNDIR, ep.getEndpointType());
+    }
+
+    @Test
+    public void testPort() throws Exception {
+        
+        String xml = 
+            "<endpoint node=\"1\" port=\"abc123\" id=\"ep1\">" +
+            "</endpoint>";
+        
+        EndpointMetadata ep = (EndpointMetadata) readObject(xml);
+        Assert.assertNotNull(ep);
+        Assert.assertEquals("1", ep.getNode());
+        Assert.assertEquals("ep1", ep.getId());
+        Assert.assertEquals(null, ep.getDescription());        
+        Assert.assertEquals("abc123", ep.getPort());
+        Assert.assertEquals(EndpointType.UNDIR, ep.getEndpointType());
+    }
+
+    @Test
+    public void testTypeIn() throws Exception {
+        
+        String xml = 
+            "<endpoint node=\"1\" type=\"in\">" +
+            "</endpoint>";
+        
+        EndpointMetadata ep = (EndpointMetadata) readObject(xml);
+        Assert.assertNotNull(ep);
+        Assert.assertEquals("1", ep.getNode());
+        Assert.assertEquals(null, ep.getId());
+        Assert.assertEquals(null, ep.getDescription());        
+        Assert.assertEquals(null, ep.getPort());
+        Assert.assertEquals(EndpointType.IN, ep.getEndpointType());
+    }
+
+    @Test
+    public void testTypeOut() throws Exception {
+        
+        String xml = 
+            "<endpoint node=\"1\" type=\"out\">" +
+            "</endpoint>";
+        
+        EndpointMetadata ep = (EndpointMetadata) readObject(xml);
+        Assert.assertNotNull(ep);
+        Assert.assertEquals("1", ep.getNode());
+        Assert.assertEquals(null, ep.getId());
+        Assert.assertEquals(null, ep.getDescription());        
+        Assert.assertEquals(null, ep.getPort());
+        Assert.assertEquals(EndpointType.OUT, ep.getEndpointType());
+    }
+
+    @Test
+    public void testTypeUndir() throws Exception {
+        
+        String xml = 
+            "<endpoint node=\"1\" type=\"undir\">" +
+            "</endpoint>";
+        
+        EndpointMetadata ep = (EndpointMetadata) readObject(xml);
+        Assert.assertNotNull(ep);
+        Assert.assertEquals("1", ep.getNode());
+        Assert.assertEquals(null, ep.getId());
+        Assert.assertEquals(null, ep.getDescription());        
+        Assert.assertEquals(null, ep.getPort());
+        Assert.assertEquals(EndpointType.UNDIR, ep.getEndpointType());
+    }
+
+    @Test
+    public void testTypeInvalid() throws Exception {
+        
+        String xml = 
+            "<endpoint node=\"1\" type=\"blaa\">" +
+            "</endpoint>";
+        
+        EndpointMetadata ep = (EndpointMetadata) readObject(xml);
+        Assert.assertNotNull(ep);
+        Assert.assertEquals("1", ep.getNode());
+        Assert.assertEquals(null, ep.getId());
+        Assert.assertEquals(null, ep.getDescription());        
+        Assert.assertEquals(null, ep.getPort());
+        Assert.assertEquals(EndpointType.UNDIR, ep.getEndpointType());
+    }
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestGraphElementParser.java b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestGraphElementParser.java
new file mode 100644
index 0000000..71b16cd
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestGraphElementParser.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.EdgeMetadata;
+import edu.uci.ics.jung.io.graphml.GraphMetadata;
+import edu.uci.ics.jung.io.graphml.NodeMetadata;
+import edu.uci.ics.jung.io.graphml.GraphMetadata.EdgeDefault;
+
+public class TestGraphElementParser extends AbstractParserTest {
+
+    @Test(expected= GraphIOException.class)
+    public void testNoEdgeDefault() throws Exception {
+        
+        String xml = 
+            "<graph/>";
+        
+        readObject(xml);
+    }
+
+    @Test
+    public void testEdgeDefaultDirected() throws Exception {
+        
+        String xml = 
+            "<graph edgedefault=\"directed\"/>";
+        
+        GraphMetadata g = (GraphMetadata) readObject(xml);
+        Assert.assertNotNull(g);
+        Assert.assertEquals(EdgeDefault.DIRECTED, g.getEdgeDefault());
+        Assert.assertEquals(null, g.getId());
+        Assert.assertEquals(null, g.getDescription());
+        Assert.assertEquals(0, g.getNodeMap().size());
+        Assert.assertEquals(0, g.getEdgeMap().size());
+        Assert.assertEquals(0, g.getHyperEdgeMap().size());
+    }
+
+    @Test
+    public void testEdgeDefaultUndirected() throws Exception {
+        
+        String xml = 
+            "<graph edgedefault=\"undirected\"/>";
+        
+        GraphMetadata g = (GraphMetadata) readObject(xml);
+        Assert.assertNotNull(g);
+        Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault());
+        Assert.assertEquals(null, g.getId());
+        Assert.assertEquals(null, g.getDescription());
+        Assert.assertEquals(0, g.getNodeMap().size());
+        Assert.assertEquals(0, g.getEdgeMap().size());
+        Assert.assertEquals(0, g.getHyperEdgeMap().size());
+    }
+
+    @Test
+    public void testDesc() throws Exception {
+        
+        String xml = 
+            "<graph edgedefault=\"undirected\">" +
+                "<desc>hello world</desc>" +
+            "</graph>";
+        
+        GraphMetadata g = (GraphMetadata) readObject(xml);
+        Assert.assertNotNull(g);
+        Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault());
+        Assert.assertEquals(null, g.getId());
+        Assert.assertEquals("hello world", g.getDescription());
+        Assert.assertEquals(0, g.getNodeMap().size());
+        Assert.assertEquals(0, g.getEdgeMap().size());
+        Assert.assertEquals(0, g.getHyperEdgeMap().size());
+    }
+
+    @Test
+    public void testNodes() throws Exception {
+        
+        String xml = 
+            "<graph edgedefault=\"undirected\">" +
+                "<node id=\"1\"/>" +
+                "<node id=\"2\"/>" +
+                "<node id=\"3\"/>" +
+            "</graph>";
+        
+        GraphMetadata g = (GraphMetadata) readObject(xml);
+        Assert.assertNotNull(g);
+        Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault());
+        Assert.assertEquals(null, g.getId());
+        Assert.assertEquals(null, g.getDescription());
+        Assert.assertEquals(3, g.getNodeMap().size());        
+        List<NodeMetadata> nodes = new ArrayList<NodeMetadata>(g.getNodeMap().values());
+        Collections.sort(nodes, new Comparator<NodeMetadata>() {
+            public int compare(NodeMetadata o1, NodeMetadata o2) {
+                return o1.getId().compareTo(o2.getId());
+            }
+        });
+        Assert.assertEquals("1", nodes.get(0).getId());
+        Assert.assertEquals("2", nodes.get(1).getId());
+        Assert.assertEquals("3", nodes.get(2).getId());
+    }
+
+    @Test
+    public void testEdges() throws Exception {
+        
+        String xml = 
+            "<graph edgedefault=\"undirected\">" +
+                "<node id=\"1\"/>" +
+                "<node id=\"2\"/>" +
+                "<node id=\"3\"/>" +
+                "<edge source=\"1\" target=\"2\"/>" +
+                "<edge source=\"2\" target=\"3\"/>" +
+            "</graph>";
+        
+        GraphMetadata g = (GraphMetadata) readObject(xml);
+        Assert.assertNotNull(g);
+        Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault());
+        Assert.assertEquals(null, g.getId());
+        Assert.assertEquals(null, g.getDescription());
+        List<EdgeMetadata> edges = new ArrayList<EdgeMetadata>(g.getEdgeMap().values());
+        Collections.sort(edges, new Comparator<EdgeMetadata>() {
+            public int compare(EdgeMetadata o1, EdgeMetadata o2) {
+                return o1.getSource().compareTo(o2.getSource());
+            }
+        });
+        Assert.assertEquals(2, edges.size());
+        Assert.assertEquals("1", edges.get(0).getSource());
+        Assert.assertEquals("2", edges.get(1).getSource());
+    }
+
+    @Test
+    public void testHyperEdges() throws Exception {
+        
+        String xml = 
+            "<graph edgedefault=\"undirected\">" +
+                "<node id=\"1\"/>" +
+                "<node id=\"2\"/>" +
+                "<node id=\"3\"/>" +
+                "<hyperedge>" +
+                    "<endpoint node=\"1\"/>" +
+                    "<endpoint node=\"2\"/>" +
+                "</hyperedge>" +
+                "<hyperedge>" +
+                    "<endpoint node=\"2\"/>" +
+                    "<endpoint node=\"3\"/>" +
+                "</hyperedge>" +
+                "<hyperedge>" +
+                    "<endpoint node=\"3\"/>" +
+                    "<endpoint node=\"1\"/>" +
+                "</hyperedge>" +
+            "</graph>";
+        
+        GraphMetadata g = (GraphMetadata) readObject(xml);
+        Assert.assertNotNull(g);
+        Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault());
+        Assert.assertEquals(null, g.getId());
+        Assert.assertEquals(null, g.getDescription());
+        Assert.assertEquals(3, g.getHyperEdgeMap().size());
+    }
+
+    @Test
+    public void testUserAttributes() throws Exception {
+        
+        String xml = 
+            "<graph edgedefault=\"undirected\" bob=\"abc123\">" +
+            "</graph>";
+        
+        GraphMetadata g = (GraphMetadata) readObject(xml);
+        Assert.assertNotNull(g);
+        Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault());
+        Assert.assertEquals(null, g.getId());
+        Assert.assertEquals(null, g.getDescription());
+        Assert.assertEquals(1, g.getProperties().size());
+        Assert.assertEquals("abc123", g.getProperty("bob"));
+    }
+
+    @Test
+    public void testData() throws Exception {
+        
+        String xml = 
+            "<graph edgedefault=\"undirected\">" +
+                "<data key=\"d1\">value1</data>" +
+                "<data key=\"d2\">value2</data>" +
+            "</graph>";
+        
+        GraphMetadata g = (GraphMetadata) readObject(xml);
+        Assert.assertNotNull(g);
+        Assert.assertEquals(EdgeDefault.UNDIRECTED, g.getEdgeDefault());
+        Assert.assertEquals(null, g.getId());
+        Assert.assertEquals(null, g.getDescription());
+        Assert.assertEquals(2, g.getProperties().size());
+        Assert.assertEquals("value1", g.getProperty("d1"));
+        Assert.assertEquals("value2", g.getProperty("d2"));
+    }
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestHyperEdgeElementParser.java b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestHyperEdgeElementParser.java
new file mode 100644
index 0000000..285aa9b
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestHyperEdgeElementParser.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import edu.uci.ics.jung.io.graphml.HyperEdgeMetadata;
+
+public class TestHyperEdgeElementParser extends AbstractParserTest {
+
+    @Test
+    public void testEmpty() throws Exception {
+        
+        String xml = 
+            "<hyperedge/>";
+        
+        HyperEdgeMetadata edge = (HyperEdgeMetadata) readObject(xml);
+        Assert.assertNotNull(edge);
+        Assert.assertEquals(null, edge.getId());
+        Assert.assertEquals(null, edge.getDescription());
+        Assert.assertEquals(0, edge.getEndpoints().size());
+    }
+
+    @Test
+    public void testId() throws Exception {
+        
+        String xml = 
+            "<hyperedge id=\"e1\"/>";
+        
+        HyperEdgeMetadata edge = (HyperEdgeMetadata) readObject(xml);
+        Assert.assertNotNull(edge);
+        Assert.assertEquals("e1", edge.getId());
+        Assert.assertEquals(null, edge.getDescription());
+        Assert.assertEquals(0, edge.getEndpoints().size());
+    }
+
+    @Test
+    public void testDesc() throws Exception {
+        
+        String xml = 
+            "<hyperedge>" +
+                "<desc>hello world</desc>" +
+            "</hyperedge>";
+        
+        HyperEdgeMetadata edge = (HyperEdgeMetadata) readObject(xml);
+        Assert.assertNotNull(edge);
+        Assert.assertEquals(null, edge.getId());
+        Assert.assertEquals("hello world", edge.getDescription());
+        Assert.assertEquals(0, edge.getEndpoints().size());
+    }
+
+    @Test
+    public void testEndpoints() throws Exception {
+        
+        String xml = 
+            "<hyperedge>" +
+                "<endpoint node=\"1\"/>" +
+                "<endpoint node=\"2\"/>" +
+                "<endpoint node=\"3\"/>" +
+            "</hyperedge>";
+        
+        HyperEdgeMetadata edge = (HyperEdgeMetadata) readObject(xml);
+        Assert.assertNotNull(edge);
+        Assert.assertEquals(null, edge.getId());
+        Assert.assertEquals(null, edge.getDescription());
+        Assert.assertEquals(3, edge.getEndpoints().size());
+        Assert.assertEquals("1", edge.getEndpoints().get(0).getNode());
+        Assert.assertEquals("2", edge.getEndpoints().get(1).getNode());
+        Assert.assertEquals("3", edge.getEndpoints().get(2).getNode());
+    }
+
+    @Test
+    public void testUserAttributes() throws Exception {
+        
+        String xml = 
+            "<hyperedge bob=\"abc123\"/>";
+        
+        HyperEdgeMetadata edge = (HyperEdgeMetadata) readObject(xml);
+        Assert.assertNotNull(edge);
+        Assert.assertEquals(null, edge.getId());
+        Assert.assertEquals(null, edge.getDescription());
+        Assert.assertEquals(0, edge.getEndpoints().size());
+        Assert.assertEquals(1, edge.getProperties().size());
+        Assert.assertEquals("abc123", edge.getProperty("bob"));
+    }
+
+    @Test
+    public void testData() throws Exception {
+        
+        String xml = 
+            "<hyperedge>" +
+                "<data key=\"d1\">value1</data>" +
+                "<data key=\"d2\">value2</data>" +
+            "</hyperedge>";
+        
+        HyperEdgeMetadata edge = (HyperEdgeMetadata) readObject(xml);
+        Assert.assertNotNull(edge);
+        Assert.assertEquals(null, edge.getId());
+        Assert.assertEquals(null, edge.getDescription());
+        Assert.assertEquals(0, edge.getEndpoints().size());
+        Assert.assertEquals(2, edge.getProperties().size());
+        Assert.assertEquals("value1", edge.getProperty("d1"));
+        Assert.assertEquals("value2", edge.getProperty("d2"));
+    }
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestKeyElementParser.java b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestKeyElementParser.java
new file mode 100644
index 0000000..fb21bbd
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestKeyElementParser.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.Key;
+
+public class TestKeyElementParser extends AbstractParserTest {
+
+    @Test(expected= GraphIOException.class)
+    public void testNoId() throws Exception {
+        
+        String xml = 
+            "<key/>";
+        
+        readObject(xml);
+    }
+
+    @Test
+    public void testId() throws Exception {
+        
+        String xml = 
+            "<key id=\"d1\"/>";
+        
+        Key key = (Key) readObject(xml);
+        Assert.assertNotNull(key);
+        Assert.assertEquals("d1", key.getId());
+        Assert.assertEquals(null, key.getDescription());
+        Assert.assertEquals(null, key.getDefaultValue());
+        Assert.assertEquals(null, key.getAttributeName());
+        Assert.assertEquals(null, key.getAttributeType());
+        Assert.assertEquals(Key.ForType.ALL, key.getForType());
+    }
+
+    @Test
+    public void testDesc() throws Exception {
+        
+        String xml = 
+            "<key id=\"d1\">" +
+                "<desc>this is my key</desc>" +
+            "</key>";
+        
+        Key key = (Key) readObject(xml);
+        Assert.assertNotNull(key);
+        Assert.assertEquals("d1", key.getId());
+        Assert.assertEquals("this is my key", key.getDescription());
+        Assert.assertEquals(null, key.getDefaultValue());
+        Assert.assertEquals(null, key.getAttributeName());
+        Assert.assertEquals(null, key.getAttributeType());
+        Assert.assertEquals(Key.ForType.ALL, key.getForType());
+    }
+
+    @Test
+    public void testDefault() throws Exception {
+        
+        String xml = 
+            "<key id=\"d1\">" +
+                "<default>yellow</default>" +
+            "</key>";
+        
+        Key key = (Key) readObject(xml);
+        Assert.assertNotNull(key);
+        Assert.assertEquals("d1", key.getId());
+        Assert.assertEquals(null, key.getDescription());
+        Assert.assertEquals("yellow", key.getDefaultValue());
+        Assert.assertEquals(null, key.getAttributeName());
+        Assert.assertEquals(null, key.getAttributeType());
+        Assert.assertEquals(Key.ForType.ALL, key.getForType());
+    }
+
+    @Test
+    public void testAttrNameType() throws Exception {
+        
+        String xml = 
+            "<key id=\"d1\" attr.name=\"myattr\" attr.type=\"double\">" +
+            "</key>";
+        
+        Key key = (Key) readObject(xml);
+        Assert.assertNotNull(key);
+        Assert.assertEquals("d1", key.getId());
+        Assert.assertEquals(null, key.getDescription());
+        Assert.assertEquals(null, key.getDefaultValue());
+        Assert.assertEquals("myattr", key.getAttributeName());
+        Assert.assertEquals("double", key.getAttributeType());
+        Assert.assertEquals(Key.ForType.ALL, key.getForType());
+    }
+
+    @Test
+    public void testForNode() throws Exception {
+        
+        String xml = 
+            "<key id=\"d1\" for=\"node\">" +
+            "</key>";
+        
+        Key key = (Key) readObject(xml);
+        Assert.assertNotNull(key);
+        Assert.assertEquals("d1", key.getId());
+        Assert.assertEquals(null, key.getDescription());
+        Assert.assertEquals(null, key.getDefaultValue());
+        Assert.assertEquals(null, key.getAttributeName());
+        Assert.assertEquals(null, key.getAttributeType());
+        Assert.assertEquals(Key.ForType.NODE, key.getForType());
+    }
+
+    @Test
+    public void testForEdge() throws Exception {
+        
+        String xml = 
+            "<key id=\"d1\" for=\"edge\">" +
+            "</key>";
+        
+        Key key = (Key) readObject(xml);
+        Assert.assertNotNull(key);
+        Assert.assertEquals("d1", key.getId());
+        Assert.assertEquals(null, key.getDescription());
+        Assert.assertEquals(null, key.getDefaultValue());
+        Assert.assertEquals(null, key.getAttributeName());
+        Assert.assertEquals(null, key.getAttributeType());
+        Assert.assertEquals(Key.ForType.EDGE, key.getForType());
+    }
+
+    @Test
+    public void testForGraph() throws Exception {
+        
+        String xml = 
+            "<key id=\"d1\" for=\"graph\">" +
+            "</key>";
+        
+        Key key = (Key) readObject(xml);
+        Assert.assertNotNull(key);
+        Assert.assertEquals("d1", key.getId());
+        Assert.assertEquals(null, key.getDescription());
+        Assert.assertEquals(null, key.getDefaultValue());
+        Assert.assertEquals(null, key.getAttributeName());
+        Assert.assertEquals(null, key.getAttributeType());
+        Assert.assertEquals(Key.ForType.GRAPH, key.getForType());
+    }
+
+    @Test
+    public void testForAll() throws Exception {
+        
+        String xml = 
+            "<key id=\"d1\" for=\"all\">" +
+            "</key>";
+        
+        Key key = (Key) readObject(xml);
+        Assert.assertNotNull(key);
+        Assert.assertEquals("d1", key.getId());
+        Assert.assertEquals(null, key.getDescription());
+        Assert.assertEquals(null, key.getDefaultValue());
+        Assert.assertEquals(null, key.getAttributeName());
+        Assert.assertEquals(null, key.getAttributeType());
+        Assert.assertEquals(Key.ForType.ALL, key.getForType());
+    }
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestNodeElementParser.java b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestNodeElementParser.java
new file mode 100644
index 0000000..d0ec22a
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestNodeElementParser.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.NodeMetadata;
+
+public class TestNodeElementParser extends AbstractParserTest {
+
+    @Test(expected= GraphIOException.class)
+    public void testNoId() throws Exception {
+        
+        String xml = 
+            "<node/>";
+        
+        readObject(xml);
+    }
+
+    @Test
+    public void testId() throws Exception {
+        
+        String xml = 
+            "<node id=\"1\"/>";
+        
+        NodeMetadata node = (NodeMetadata) readObject(xml);
+        Assert.assertNotNull(node);
+        Assert.assertEquals("1", node.getId());
+        Assert.assertEquals(null, node.getDescription());
+        Assert.assertEquals(0, node.getPorts().size());
+    }
+
+    @Test
+    public void testDesc() throws Exception {
+        
+        String xml = 
+            "<node id=\"1\">" +
+                "<desc>this is my node</desc>" +
+            "</node>";
+        
+        NodeMetadata node = (NodeMetadata) readObject(xml);
+        Assert.assertNotNull(node);
+        Assert.assertEquals("1", node.getId());
+        Assert.assertEquals("this is my node", node.getDescription());
+        Assert.assertEquals(0, node.getPorts().size());
+    }
+
+    @Test
+    public void testPort() throws Exception {
+                    
+        String xml = 
+            "<node id=\"1\">" +
+                "<desc>this is my node</desc>" +
+                "<port name=\"p1\">" +
+                    "<desc>port 1</desc>" +
+                "</port>" +
+            "</node>";
+        
+        NodeMetadata node = (NodeMetadata) readObject(xml);
+        Assert.assertNotNull(node);
+        Assert.assertEquals("1", node.getId());
+        Assert.assertEquals("this is my node", node.getDescription());
+        Assert.assertEquals(1, node.getPorts().size());
+        Assert.assertEquals("p1", node.getPorts().get(0).getName());
+    }
+
+    @Test
+    public void testMultiPort() throws Exception {
+        
+        String xml = 
+            "<node id=\"1\">" +
+                "<desc>this is my node</desc>" +
+                "<port name=\"p1\"/>" +
+                "<port name=\"p2\"/>" +
+                "<port name=\"p3\"/>" +
+                "<port name=\"p4\"/>" +
+            "</node>";
+        
+        NodeMetadata node = (NodeMetadata) readObject(xml);
+        Assert.assertNotNull(node);
+        Assert.assertEquals("1", node.getId());
+        Assert.assertEquals("this is my node", node.getDescription());
+        Assert.assertEquals(4, node.getPorts().size());
+        Assert.assertEquals("p1", node.getPorts().get(0).getName());
+        Assert.assertEquals("p2", node.getPorts().get(1).getName());
+        Assert.assertEquals("p3", node.getPorts().get(2).getName());
+        Assert.assertEquals("p4", node.getPorts().get(3).getName());
+    }
+
+    @Test
+    public void testUserAttributes() throws Exception {
+        
+        String xml = 
+            "<node id=\"1\" bob=\"abc123\"/>";
+        
+        NodeMetadata node = (NodeMetadata) readObject(xml);
+        Assert.assertNotNull(node);
+        Assert.assertEquals("1", node.getId());
+        Assert.assertEquals(1, node.getProperties().size());
+        Assert.assertEquals("abc123", node.getProperty("bob"));
+        Assert.assertEquals(0, node.getPorts().size());
+    }
+
+    @Test
+    public void testData() throws Exception {
+        
+        String xml = 
+            "<node id=\"1\">" +
+                "<data key=\"d1\">value1</data>" +
+                "<data key=\"d2\">value2</data>" +
+            "</node>";
+        
+        NodeMetadata node = (NodeMetadata) readObject(xml);
+        Assert.assertNotNull(node);
+        Assert.assertEquals("1", node.getId());
+        Assert.assertEquals(2, node.getProperties().size());
+        Assert.assertEquals("value1", node.getProperty("d1"));
+        Assert.assertEquals("value2", node.getProperty("d2"));
+        Assert.assertEquals(0, node.getPorts().size());
+    }
+}
diff --git a/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestPortElementParser.java b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestPortElementParser.java
new file mode 100644
index 0000000..75e5fa0
--- /dev/null
+++ b/jung-io/src/test/java/edu/uci/ics/jung/io/graphml/parser/TestPortElementParser.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+
+package edu.uci.ics.jung.io.graphml.parser;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import edu.uci.ics.jung.io.GraphIOException;
+import edu.uci.ics.jung.io.graphml.PortMetadata;
+
+public class TestPortElementParser extends AbstractParserTest {
+
+    @Test(expected= GraphIOException.class)
+    public void testNoName() throws Exception {
+        
+        String xml = 
+            "<port/>";
+        
+        readObject(xml);
+    }
+
+    @Test
+    public void testName() throws Exception {
+        
+        String xml = 
+            "<port name=\"p1\"/>";
+        
+        PortMetadata port = (PortMetadata) readObject(xml);
+        Assert.assertNotNull(port);
+        Assert.assertEquals("p1", port.getName());
+        Assert.assertEquals(null, port.getDescription());
+    }
+
+    @Test
+    public void testDesc() throws Exception {
+        
+        String xml = 
+            "<port name=\"p1\">" +
+                "<desc>this is my port</desc>" +
+            "</port>";
+        
+        PortMetadata port = (PortMetadata) readObject(xml);
+        Assert.assertNotNull(port);
+        Assert.assertEquals("p1", port.getName());
+        Assert.assertEquals("this is my port", port.getDescription());
+    }
+
+    @Test
+    public void testUserAttributes() throws Exception {
+        
+        String xml = 
+            "<port name=\"p1\" bob=\"abc123\"/>";
+        
+        PortMetadata port = (PortMetadata) readObject(xml);
+        Assert.assertNotNull(port);
+        Assert.assertEquals("p1", port.getName());
+        Assert.assertEquals(1, port.getProperties().size());
+        Assert.assertEquals("abc123", port.getProperty("bob"));
+    }
+
+    @Test
+    public void testData() throws Exception {
+        
+        String xml =
+            "<port name=\"p1\">" +
+                "<data key=\"d1\">value1</data>" +
+                "<data key=\"d2\">value2</data>" +
+            "</port>";
+        
+        PortMetadata port = (PortMetadata) readObject(xml);
+        Assert.assertNotNull(port);
+        Assert.assertEquals("p1", port.getName());
+        Assert.assertEquals(2, port.getProperties().size());
+        Assert.assertEquals("value1", port.getProperty("d1"));
+        Assert.assertEquals("value2", port.getProperty("d2"));
+    }
+}
diff --git a/jung-io/src/test/resources/attributes.graphml b/jung-io/src/test/resources/attributes.graphml
new file mode 100644
index 0000000..2616bd8
--- /dev/null
+++ b/jung-io/src/test/resources/attributes.graphml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns"  
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+      xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns 
+        http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
+  <key id="d0" for="node" attr.name="color" attr.type="string">
+    <default>yellow</default>
+  </key>
+  <key id="d1" for="edge" attr.name="weight" attr.type="double"/>
+  <graph id="G" edgedefault="undirected">
+    <node id="n0">
+      <data key="d0">green</data>
+    </node>
+    <node id="n1"/>
+    <node id="n2">
+      <data key="d0">blue</data>
+    </node>
+    <node id="n3">
+      <data key="d0">red</data>
+    </node>
+    <node id="n4"/>
+    <node id="n5">
+      <data key="d0">turquoise</data>
+    </node>
+    <edge id="e0" source="n0" target="n2">
+      <data key="d1">1.0</data>
+    </edge>
+    <edge id="e1" source="n0" target="n1">
+      <data key="d1">1.0</data>
+    </edge>
+    <edge id="e2" source="n1" target="n3">
+      <data key="d1">2.0</data>
+    </edge>
+    <edge id="e3" source="n3" target="n2"/>
+    <edge id="e4" source="n2" target="n4"/>
+    <edge id="e5" source="n3" target="n5"/>
+    <edge id="e6" source="n5" target="n4">
+      <data key="d1">1.1</data>
+    </edge>
+  </graph>
+</graphml>
\ No newline at end of file
diff --git a/jung-io/src/test/resources/edu/uci/ics/jung/io/graphml/attributes.graphml b/jung-io/src/test/resources/edu/uci/ics/jung/io/graphml/attributes.graphml
new file mode 100644
index 0000000..2616bd8
--- /dev/null
+++ b/jung-io/src/test/resources/edu/uci/ics/jung/io/graphml/attributes.graphml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns"  
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+      xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns 
+        http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
+  <key id="d0" for="node" attr.name="color" attr.type="string">
+    <default>yellow</default>
+  </key>
+  <key id="d1" for="edge" attr.name="weight" attr.type="double"/>
+  <graph id="G" edgedefault="undirected">
+    <node id="n0">
+      <data key="d0">green</data>
+    </node>
+    <node id="n1"/>
+    <node id="n2">
+      <data key="d0">blue</data>
+    </node>
+    <node id="n3">
+      <data key="d0">red</data>
+    </node>
+    <node id="n4"/>
+    <node id="n5">
+      <data key="d0">turquoise</data>
+    </node>
+    <edge id="e0" source="n0" target="n2">
+      <data key="d1">1.0</data>
+    </edge>
+    <edge id="e1" source="n0" target="n1">
+      <data key="d1">1.0</data>
+    </edge>
+    <edge id="e2" source="n1" target="n3">
+      <data key="d1">2.0</data>
+    </edge>
+    <edge id="e3" source="n3" target="n2"/>
+    <edge id="e4" source="n2" target="n4"/>
+    <edge id="e5" source="n3" target="n5"/>
+    <edge id="e6" source="n5" target="n4">
+      <data key="d1">1.1</data>
+    </edge>
+  </graph>
+</graphml>
\ No newline at end of file
diff --git a/jung-io/src/test/resources/edu/uci/ics/jung/io/graphml/hyper.graphml b/jung-io/src/test/resources/edu/uci/ics/jung/io/graphml/hyper.graphml
new file mode 100644
index 0000000..b5d78be
--- /dev/null
+++ b/jung-io/src/test/resources/edu/uci/ics/jung/io/graphml/hyper.graphml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
+  <graph id="G" edgedefault="undirected">
+    <node id="n0"/>
+    <node id="n1"/>
+    <node id="n2"/>
+    <node id="n3"/>
+    <node id="n4"/>
+    <node id="n5"/>
+    <node id="n6"/>
+    <hyperedge>
+       <endpoint node="n0"/>
+       <endpoint node="n1"/>
+       <endpoint node="n2"/>
+     </hyperedge>
+    <hyperedge>
+       <endpoint node="n3"/>
+       <endpoint node="n4"/>
+       <endpoint node="n5"/>
+       <endpoint node="n6"/>
+     </hyperedge>
+    <hyperedge>
+       <endpoint node="n1"/>
+       <endpoint node="n3"/>
+     </hyperedge>
+    <edge source="n0" target="n4"/>
+  </graph>
+</graphml>
\ No newline at end of file
diff --git a/jung-io/src/test/resources/edu/uci/ics/jung/io/graphml/multigraph.graphml b/jung-io/src/test/resources/edu/uci/ics/jung/io/graphml/multigraph.graphml
new file mode 100644
index 0000000..171de6c
--- /dev/null
+++ b/jung-io/src/test/resources/edu/uci/ics/jung/io/graphml/multigraph.graphml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns"  
+      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+      xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns 
+        http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
+  <key id="d0" for="node" attr.name="color" attr.type="string">
+    <default>yellow</default>
+  </key>
+  <key id="d1" for="edge" attr.name="weight" attr.type="double"/>
+  <graph id="G" edgedefault="undirected">
+    <node id="n0">
+      <data key="d0">green</data>
+    </node>
+    <node id="n1"/>
+    <node id="n2">
+      <data key="d0">blue</data>
+    </node>
+    <node id="n3">
+      <data key="d0">red</data>
+    </node>
+    <node id="n4"/>
+    <node id="n5">
+      <data key="d0">turquoise</data>
+    </node>
+    <edge id="e0" source="n0" target="n2">
+      <data key="d1">1.0</data>
+    </edge>
+    <edge id="e1" source="n0" target="n1">
+      <data key="d1">1.0</data>
+    </edge>
+    <edge id="e2" source="n1" target="n3">
+      <data key="d1">2.0</data>
+    </edge>
+    <edge id="e3" source="n3" target="n2"/>
+    <edge id="e4" source="n2" target="n4"/>
+    <edge id="e5" source="n3" target="n5"/>
+    <edge id="e6" source="n5" target="n4">
+      <data key="d1">1.1</data>
+    </edge>
+  </graph>
+  <graph id="G" edgedefault="undirected">
+    <node id="n0"/>
+    <node id="n1"/>
+    <node id="n2"/>
+    <node id="n3"/>
+    <node id="n4"/>
+    <node id="n5"/>
+    <node id="n6"/>
+    <hyperedge>
+       <endpoint node="n0"/>
+       <endpoint node="n1"/>
+       <endpoint node="n2"/>
+     </hyperedge>
+    <hyperedge>
+       <endpoint node="n3"/>
+       <endpoint node="n4"/>
+       <endpoint node="n5"/>
+       <endpoint node="n6"/>
+     </hyperedge>
+    <hyperedge>
+       <endpoint node="n1"/>
+       <endpoint node="n3"/>
+     </hyperedge>
+    <edge source="n0" target="n4"/>
+  </graph>
+</graphml>
\ No newline at end of file
diff --git a/jung-io/src/test/resources/hyper.graphml b/jung-io/src/test/resources/hyper.graphml
new file mode 100644
index 0000000..b5d78be
--- /dev/null
+++ b/jung-io/src/test/resources/hyper.graphml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
+  <graph id="G" edgedefault="undirected">
+    <node id="n0"/>
+    <node id="n1"/>
+    <node id="n2"/>
+    <node id="n3"/>
+    <node id="n4"/>
+    <node id="n5"/>
+    <node id="n6"/>
+    <hyperedge>
+       <endpoint node="n0"/>
+       <endpoint node="n1"/>
+       <endpoint node="n2"/>
+     </hyperedge>
+    <hyperedge>
+       <endpoint node="n3"/>
+       <endpoint node="n4"/>
+       <endpoint node="n5"/>
+       <endpoint node="n6"/>
+     </hyperedge>
+    <hyperedge>
+       <endpoint node="n1"/>
+       <endpoint node="n3"/>
+     </hyperedge>
+    <edge source="n0" target="n4"/>
+  </graph>
+</graphml>
\ No newline at end of file
diff --git a/jung-samples-2.0.1-sources.jar b/jung-samples-2.0.1-sources.jar
deleted file mode 100644
index f776fa5..0000000
Binary files a/jung-samples-2.0.1-sources.jar and /dev/null differ
diff --git a/jung-samples/pom.xml b/jung-samples/pom.xml
new file mode 100644
index 0000000..ce3932f
--- /dev/null
+++ b/jung-samples/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>net.sf.jung</groupId>
+    <artifactId>jung-parent</artifactId>
+    <version>2.1.1</version>
+  </parent>
+  <artifactId>jung-samples</artifactId>
+  <name>JUNG - Samples</name>
+  <description>
+    Sample programs using JUNG. Nearly all JUNG capabilities are demonstrated here.
+    Please study the source code for these examples prior to asking how to do something.
+  </description>
+
+  <dependencies>
+    <dependency>
+      <groupId>net.sf.jung</groupId>
+      <artifactId>jung-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>net.sf.jung</groupId>
+      <artifactId>jung-visualization</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>net.sf.jung</groupId>
+      <artifactId>jung-graph-impl</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>net.sf.jung</groupId>
+      <artifactId>jung-algorithms</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>net.sf.jung</groupId>
+      <artifactId>jung-io</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifest>
+              <mainClass>edu.uci.ics.jung.samples.VertexImageShaperDemo</mainClass>
+              <addClasspath>true</addClasspath>
+            </manifest>
+          </archive>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <profiles>
+    <profile>
+      <!-- build with assembly - this is the default. Run with "-P!assembly" 
+        to disable. TODO(cgruber) should this be a shaded uber jar, or assembled 
+        via the maven-assembly-plugin? -->
+      <id>assembly</id>
+      <activation>
+        <activeByDefault>true</activeByDefault>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <artifactId>maven-dependency-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>copy-dependencies</id>
+                <phase>package</phase>
+                <goals>
+                  <goal>copy-dependencies</goal>
+                </goals>
+                <configuration>
+                  <outputDirectory>${project.build.directory}</outputDirectory>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>
diff --git a/jung-samples/simple.graphml b/jung-samples/simple.graphml
new file mode 100644
index 0000000..11cc746
--- /dev/null
+++ b/jung-samples/simple.graphml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This file was written by the JAVA GraphML Library.-->
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
+  <graph id="G" edgedefault="directed">
+    <node id="n0"/>
+    <node id="n1"/>
+    <node id="n2"/>
+    <node id="n3"/>
+    <node id="n4"/>
+    <node id="n5"/>
+    <node id="n6"/>
+    <node id="n7"/>
+    <node id="n8"/>
+    <node id="n9"/>
+    <node id="n10"/>
+    <edge source="n0" target="n2"/>
+    <edge source="n1" target="n2"/>
+    <edge source="n2" target="n3"/>
+    <edge source="n3" target="n5"/>
+    <edge source="n3" target="n4"/>
+    <edge source="n4" target="n6"/>
+    <edge source="n6" target="n5"/>
+    <edge source="n5" target="n7"/>
+    <edge source="n6" target="n8"/>
+    <edge source="n8" target="n7"/>
+    <edge source="n8" target="n9"/>
+    <edge source="n8" target="n10"/>
+  </graph>
+</graphml>
diff --git a/jung-samples/src/main/assembly/assembly.xml b/jung-samples/src/main/assembly/assembly.xml
new file mode 100644
index 0000000..cbb3af8
--- /dev/null
+++ b/jung-samples/src/main/assembly/assembly.xml
@@ -0,0 +1,14 @@
+<assembly>
+  <id>dependencies</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <dependencySets>
+    <dependencySet>
+      <outputDirectory>/</outputDirectory>
+      <unpack>false</unpack>
+      <scope>runtime</scope>
+    </dependencySet>
+  </dependencySets>
+</assembly>
\ No newline at end of file
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/AddNodeDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/AddNodeDemo.java
new file mode 100644
index 0000000..6afaf49
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/AddNodeDemo.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on May 10, 2004
+ */
+
+
+package edu.uci.ics.jung.samples;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JRootPane;
+
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.layout.AbstractLayout;
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.FRLayout2;
+import edu.uci.ics.jung.algorithms.layout.SpringLayout;
+import edu.uci.ics.jung.algorithms.layout.util.Relaxer;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.ObservableGraph;
+import edu.uci.ics.jung.graph.event.GraphEvent;
+import edu.uci.ics.jung.graph.event.GraphEventListener;
+import edu.uci.ics.jung.graph.util.Graphs;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+
+/**
+ * Demonstrates visualization of a graph being actively updated.
+ *
+ * @author danyelf
+ */
+public class AddNodeDemo extends javax.swing.JApplet {
+
+    /**
+	 *
+	 */
+	private static final long serialVersionUID = -5345319851341875800L;
+
+	private Graph<Number,Number> g = null;
+
+    private VisualizationViewer<Number,Number> vv = null;
+
+    private AbstractLayout<Number,Number> layout = null;
+
+    Timer timer;
+
+    boolean done;
+
+    protected JButton switchLayout;
+
+//    public static final LengthFunction<Number> UNITLENGTHFUNCTION = new SpringLayout.UnitLengthFunction<Number>(
+//            100);
+    public static final int EDGE_LENGTH = 100;
+
+    @Override
+    public void init() {
+
+        //create a graph
+    	Graph<Number,Number> ig = Graphs.<Number,Number>synchronizedDirectedGraph(new DirectedSparseMultigraph<Number,Number>());
+
+        ObservableGraph<Number,Number> og = new ObservableGraph<Number,Number>(ig);
+        og.addGraphEventListener(new GraphEventListener<Number,Number>() {
+
+			public void handleGraphEvent(GraphEvent<Number, Number> evt) {
+				System.err.println("got "+evt);
+
+			}});
+        this.g = og;
+        //create a graphdraw
+        layout = new FRLayout2<Number,Number>(g);
+//        ((FRLayout)layout).setMaxIterations(200);
+
+        vv = new VisualizationViewer<Number,Number>(layout, new Dimension(600,600));
+
+        JRootPane rp = this.getRootPane();
+        rp.putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE);
+
+        getContentPane().setLayout(new BorderLayout());
+        getContentPane().setBackground(java.awt.Color.lightGray);
+        getContentPane().setFont(new Font("Serif", Font.PLAIN, 12));
+
+        vv.getModel().getRelaxer().setSleepTime(500);
+        vv.setGraphMouse(new DefaultModalGraphMouse<Number,Number>());
+
+        vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR);
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        vv.setForeground(Color.white);
+        getContentPane().add(vv);
+        switchLayout = new JButton("Switch to SpringLayout");
+        switchLayout.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent ae) {
+            	Dimension d = new Dimension(600,600);
+                if (switchLayout.getText().indexOf("Spring") > 0) {
+                    switchLayout.setText("Switch to FRLayout");
+                    layout = new SpringLayout<Number,Number>(g,
+                        Functions.<Integer>constant(EDGE_LENGTH));
+                    layout.setSize(d);
+                    vv.getModel().setGraphLayout(layout, d);
+                } else {
+                    switchLayout.setText("Switch to SpringLayout");
+                    layout = new FRLayout<Number,Number>(g, d);
+                    vv.getModel().setGraphLayout(layout, d);
+                }
+            }
+        });
+
+        getContentPane().add(switchLayout, BorderLayout.SOUTH);
+
+        timer = new Timer();
+    }
+
+    @Override
+    public void start() {
+        validate();
+        //set timer so applet will change
+        timer.schedule(new RemindTask(), 1000, 1000); //subsequent rate
+        vv.repaint();
+    }
+
+    Integer v_prev = null;
+
+    public void process() {
+
+        try {
+
+            if (g.getVertexCount() < 100) {
+            	layout.lock(true);
+                //add a vertex
+                Integer v1 = new Integer(g.getVertexCount());
+
+                Relaxer relaxer = vv.getModel().getRelaxer();
+                relaxer.pause();
+                g.addVertex(v1);
+                System.err.println("added node " + v1);
+
+                // wire it to some edges
+                if (v_prev != null) {
+                    g.addEdge(g.getEdgeCount(), v_prev, v1);
+                    // let's connect to a random vertex, too!
+                    int rand = (int) (Math.random() * g.getVertexCount());
+                    g.addEdge(g.getEdgeCount(), v1, rand);
+                }
+
+                v_prev = v1;
+
+                layout.initialize();
+                relaxer.resume();
+                layout.lock(false);
+            } else {
+            	done = true;
+            }
+
+        } catch (Exception e) {
+            System.out.println(e);
+
+        }
+    }
+
+    class RemindTask extends TimerTask {
+
+        @Override
+        public void run() {
+            process();
+            if(done) cancel();
+
+        }
+    }
+
+    public static void main(String[] args) {
+    	AddNodeDemo and = new AddNodeDemo();
+    	JFrame frame = new JFrame();
+    	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+    	frame.getContentPane().add(and);
+
+    	and.init();
+    	and.start();
+    	frame.pack();
+    	frame.setVisible(true);
+    }
+}
\ No newline at end of file
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/AnimatingAddNodeDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/AnimatingAddNodeDemo.java
new file mode 100644
index 0000000..25e5731
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/AnimatingAddNodeDemo.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ */
+
+
+package edu.uci.ics.jung.samples;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JRootPane;
+
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.layout.AbstractLayout;
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.SpringLayout;
+import edu.uci.ics.jung.algorithms.layout.StaticLayout;
+import edu.uci.ics.jung.algorithms.layout.util.Relaxer;
+import edu.uci.ics.jung.algorithms.layout.util.VisRunner;
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.ObservableGraph;
+import edu.uci.ics.jung.graph.event.GraphEvent;
+import edu.uci.ics.jung.graph.event.GraphEventListener;
+import edu.uci.ics.jung.graph.util.Graphs;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.layout.LayoutTransition;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+import edu.uci.ics.jung.visualization.util.Animator;
+
+/**
+ * A variation of AddNodeDemo that animates transitions between graph states.
+ *
+ * @author Tom Nelson
+ */
+public class AnimatingAddNodeDemo extends javax.swing.JApplet {
+
+    /**
+	 *
+	 */
+	private static final long serialVersionUID = -5345319851341875800L;
+
+	private Graph<Number,Number> g = null;
+
+    private VisualizationViewer<Number,Number> vv = null;
+
+    private AbstractLayout<Number,Number> layout = null;
+
+    Timer timer;
+
+    boolean done;
+
+    protected JButton switchLayout;
+
+    public static final int EDGE_LENGTH = 100;
+
+    @Override
+    public void init() {
+
+        //create a graph
+    	Graph<Number,Number> ig = Graphs.<Number,Number>synchronizedDirectedGraph(new DirectedSparseMultigraph<Number,Number>());
+
+        ObservableGraph<Number,Number> og = new ObservableGraph<Number,Number>(ig);
+        og.addGraphEventListener(new GraphEventListener<Number,Number>() {
+
+			public void handleGraphEvent(GraphEvent<Number, Number> evt) {
+				System.err.println("got "+evt);
+
+			}});
+        this.g = og;
+        //create a graphdraw
+        layout = new FRLayout<Number,Number>(g);
+        layout.setSize(new Dimension(600,600));
+		Relaxer relaxer = new VisRunner((IterativeContext)layout);
+		relaxer.stop();
+		relaxer.prerelax();
+
+		Layout<Number,Number> staticLayout =
+			new StaticLayout<Number,Number>(g, layout);
+
+        vv = new VisualizationViewer<Number,Number>(staticLayout, new Dimension(600,600));
+
+        JRootPane rp = this.getRootPane();
+        rp.putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE);
+
+        getContentPane().setLayout(new BorderLayout());
+        getContentPane().setBackground(java.awt.Color.lightGray);
+        getContentPane().setFont(new Font("Serif", Font.PLAIN, 12));
+
+        vv.setGraphMouse(new DefaultModalGraphMouse<Number,Number>());
+
+        vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR);
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        vv.setForeground(Color.white);
+
+        vv.addComponentListener(new ComponentAdapter() {
+
+			/**
+			 * @see java.awt.event.ComponentAdapter#componentResized(java.awt.event.ComponentEvent)
+			 */
+			@Override
+			public void componentResized(ComponentEvent arg0) {
+				super.componentResized(arg0);
+				System.err.println("resized");
+				layout.setSize(arg0.getComponent().getSize());
+			}});
+
+        getContentPane().add(vv);
+        switchLayout = new JButton("Switch to SpringLayout");
+        switchLayout.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent ae) {
+            	Dimension d = vv.getSize();//new Dimension(600,600);
+                if (switchLayout.getText().indexOf("Spring") > 0) {
+                    switchLayout.setText("Switch to FRLayout");
+                    layout =
+                    	new SpringLayout<Number,Number>(g, Functions.constant(EDGE_LENGTH));
+                    layout.setSize(d);
+            		Relaxer relaxer = new VisRunner((IterativeContext)layout);
+            		relaxer.stop();
+            		relaxer.prerelax();
+            		StaticLayout<Number,Number> staticLayout =
+            			new StaticLayout<Number,Number>(g, layout);
+    				LayoutTransition<Number,Number> lt =
+    					new LayoutTransition<Number,Number>(vv, vv.getGraphLayout(),
+    							staticLayout);
+    				Animator animator = new Animator(lt);
+    				animator.start();
+    				vv.repaint();
+
+                } else {
+                    switchLayout.setText("Switch to SpringLayout");
+                    layout = new FRLayout<Number,Number>(g, d);
+                    layout.setSize(d);
+            		Relaxer relaxer = new VisRunner((IterativeContext)layout);
+            		relaxer.stop();
+            		relaxer.prerelax();
+            		StaticLayout<Number,Number> staticLayout =
+            			new StaticLayout<Number,Number>(g, layout);
+    				LayoutTransition<Number,Number> lt =
+    					new LayoutTransition<Number,Number>(vv, vv.getGraphLayout(),
+    							staticLayout);
+    				Animator animator = new Animator(lt);
+    				animator.start();
+    				vv.repaint();
+
+                }
+            }
+        });
+
+        getContentPane().add(switchLayout, BorderLayout.SOUTH);
+
+        timer = new Timer();
+    }
+
+    @Override
+    public void start() {
+        validate();
+        //set timer so applet will change
+        timer.schedule(new RemindTask(), 1000, 1000); //subsequent rate
+        vv.repaint();
+    }
+
+    Integer v_prev = null;
+
+    public void process() {
+
+    	vv.getRenderContext().getPickedVertexState().clear();
+    	vv.getRenderContext().getPickedEdgeState().clear();
+        try {
+
+            if (g.getVertexCount() < 100) {
+                //add a vertex
+                Integer v1 = new Integer(g.getVertexCount());
+
+                g.addVertex(v1);
+                vv.getRenderContext().getPickedVertexState().pick(v1, true);
+
+                // wire it to some edges
+                if (v_prev != null) {
+                	Integer edge = g.getEdgeCount();
+                	vv.getRenderContext().getPickedEdgeState().pick(edge, true);
+                    g.addEdge(edge, v_prev, v1);
+                    // let's connect to a random vertex, too!
+                    int rand = (int) (Math.random() * g.getVertexCount());
+                    edge = g.getEdgeCount();
+                	vv.getRenderContext().getPickedEdgeState().pick(edge, true);
+                   g.addEdge(edge, v1, rand);
+                }
+
+                v_prev = v1;
+
+                layout.initialize();
+
+        		Relaxer relaxer = new VisRunner((IterativeContext)layout);
+        		relaxer.stop();
+        		relaxer.prerelax();
+        		StaticLayout<Number,Number> staticLayout =
+        			new StaticLayout<Number,Number>(g, layout);
+				LayoutTransition<Number,Number> lt =
+					new LayoutTransition<Number,Number>(vv, vv.getGraphLayout(),
+							staticLayout);
+				Animator animator = new Animator(lt);
+				animator.start();
+//				vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
+				vv.repaint();
+
+            } else {
+            	done = true;
+            }
+
+        } catch (Exception e) {
+            System.out.println(e);
+
+        }
+    }
+
+    class RemindTask extends TimerTask {
+
+        @Override
+        public void run() {
+            process();
+            if(done) cancel();
+
+        }
+    }
+
+    public static void main(String[] args) {
+    	AnimatingAddNodeDemo and = new AnimatingAddNodeDemo();
+    	JFrame frame = new JFrame();
+    	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+    	frame.getContentPane().add(and);
+
+    	and.init();
+    	and.start();
+    	frame.pack();
+    	frame.setVisible(true);
+    }
+}
\ No newline at end of file
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/AnnotationsDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/AnnotationsDemo.java
new file mode 100644
index 0000000..7ee4ef4
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/AnnotationsDemo.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.VisualizationModel;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.VisualizationServer.Paintable;
+import edu.uci.ics.jung.visualization.annotations.AnnotatingGraphMousePlugin;
+import edu.uci.ics.jung.visualization.annotations.AnnotatingModalGraphMouse;
+import edu.uci.ics.jung.visualization.annotations.AnnotationControls;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+
+/**
+ * Demonstrates annotation of graph elements.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class AnnotationsDemo<V, E> extends JApplet {
+
+    static final String instructions = 
+        "<html>"+
+        "<b><h2><center>Instructions for Annotations</center></h2></b>"+
+        "<p>The Annotation Controls allow you to select:"+
+        "<ul>"+
+        "<li>Shape"+
+        "<li>Color"+
+        "<li>Fill (or outline)"+
+        "<li>Above or below (UPPER/LOWER) the graph display"+
+        "</ul>"+
+        "<p>Mouse Button one press starts a Shape,"+
+        "<p>drag and release to complete."+
+        "<p>Mouse Button three pops up an input dialog"+
+        "<p>for text. This will create a text annotation."+
+        "<p>You may use html for multi-line, etc."+
+        "<p>You may even use an image tag and image url"+
+        "<p>to put an image in the annotation."+
+        "<p><p>"+
+        "<p>To remove an annotation, shift-click on it"+
+        "<p>in the Annotations mode."+
+        "<p>If there is overlap, the Annotation with center"+
+        "<p>closest to the mouse point will be removed.";
+    
+    JDialog helpDialog;
+    
+    Paintable viewGrid;
+    
+    /**
+     * create an instance of a simple graph in two views with controls to
+     * demo the features.
+     * 
+     */
+    public AnnotationsDemo() {
+        
+        // create a simple graph for the demo
+        Graph<String, Number> graph = TestGraphs.getOneComponentGraph();
+        
+        // the preferred sizes for the two views
+        Dimension preferredSize1 = new Dimension(600,600);
+        
+        // create one layout for the graph
+        FRLayout<String,Number> layout = new FRLayout<String,Number>(graph);
+        layout.setMaxIterations(500);
+        
+        VisualizationModel<String,Number> vm =
+            new DefaultVisualizationModel<String,Number>(layout, preferredSize1);
+        
+        // create 2 views that share the same model
+        final VisualizationViewer<String,Number> vv = 
+            new VisualizationViewer<String,Number>(vm, preferredSize1);
+        vv.setBackground(Color.white);
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv.getPickedEdgeState(), Color.black, Color.cyan));
+        vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(vv.getPickedVertexState(), Color.red, Color.yellow));
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR);
+        
+        // add default listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        Container content = getContentPane();
+        Container panel = new JPanel(new BorderLayout());
+        
+        GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv);
+        panel.add(gzsp);
+        
+        helpDialog = new JDialog();
+        helpDialog.getContentPane().add(new JLabel(instructions));
+        
+        RenderContext<String,Number> rc = vv.getRenderContext();
+        AnnotatingGraphMousePlugin<String,Number> annotatingPlugin =
+        	new AnnotatingGraphMousePlugin<String,Number>(rc);
+        // create a GraphMouse for the main view
+        // 
+        final AnnotatingModalGraphMouse<String,Number> graphMouse = 
+        	new AnnotatingModalGraphMouse<String,Number>(rc, annotatingPlugin);
+        vv.setGraphMouse(graphMouse);
+        vv.addKeyListener(graphMouse.getModeKeyListener());
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        JComboBox<Mode> modeBox = graphMouse.getModeComboBox();
+        modeBox.setSelectedItem(ModalGraphMouse.Mode.ANNOTATING);
+        
+        JButton help = new JButton("Help");
+        help.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                helpDialog.pack();
+                helpDialog.setVisible(true);
+            }
+        });
+
+        JPanel controls = new JPanel();
+        JPanel zoomControls = new JPanel();
+        zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        zoomControls.add(plus);
+        zoomControls.add(minus);
+        controls.add(zoomControls);
+        
+        JPanel modeControls = new JPanel();
+        modeControls.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
+        modeControls.add(graphMouse.getModeComboBox());
+        controls.add(modeControls);
+        
+        JPanel annotationControlPanel = new JPanel();
+        annotationControlPanel.setBorder(BorderFactory.createTitledBorder("Annotation Controls"));
+        
+        AnnotationControls<String,Number> annotationControls = 
+            new AnnotationControls<String,Number>(annotatingPlugin);
+        
+        annotationControlPanel.add(annotationControls.getAnnotationsToolBar());
+        controls.add(annotationControlPanel);
+        
+        JPanel helpControls = new JPanel();
+        helpControls.setBorder(BorderFactory.createTitledBorder("Help"));
+        helpControls.add(help);
+        controls.add(helpControls);
+        content.add(panel);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+    
+    public static void main(String[] args) {
+        JFrame f = new JFrame();
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.getContentPane().add(new AnnotationsDemo<String,Number>());
+        f.pack();
+        f.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/BalloonLayoutDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/BalloonLayoutDemo.java
new file mode 100644
index 0000000..738c506
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/BalloonLayoutDemo.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridLayout;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JToggleButton;
+
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.layout.BalloonLayout;
+import edu.uci.ics.jung.algorithms.layout.TreeLayout;
+import edu.uci.ics.jung.graph.DelegateForest;
+import edu.uci.ics.jung.graph.DelegateTree;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Forest;
+import edu.uci.ics.jung.graph.Tree;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalLensGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.EdgeShape;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.layout.LayoutTransition;
+import edu.uci.ics.jung.visualization.transform.LensSupport;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformerDecorator;
+import edu.uci.ics.jung.visualization.transform.shape.HyperbolicShapeTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.ViewLensSupport;
+import edu.uci.ics.jung.visualization.util.Animator;
+
+/**
+ * Demonstrates the visualization of a Tree using TreeLayout
+ * and BalloonLayout. An examiner lens performing a hyperbolic
+ * transformation of the view is also included.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class BalloonLayoutDemo extends JApplet {
+
+    /**
+     * the graph
+     */
+	Forest<String,Integer> graph;
+
+	Supplier<DirectedGraph<String,Integer>> graphFactory = 
+		new Supplier<DirectedGraph<String,Integer>>() {
+
+		public DirectedGraph<String, Integer> get() {
+			return new DirectedSparseMultigraph<String,Integer>();
+		}
+	};
+
+	Supplier<Tree<String,Integer>> treeFactory =
+		new Supplier<Tree<String,Integer>> () {
+
+		public Tree<String, Integer> get() {
+			return new DelegateTree<String,Integer>(graphFactory);
+		}
+	};
+
+	Supplier<Integer> edgeFactory = new Supplier<Integer>() {
+		int i=0;
+		public Integer get() {
+			return i++;
+		}};
+    
+    Supplier<String> vertexFactory = new Supplier<String>() {
+    	int i=0;
+		public String get() {
+			return "V"+i++;
+		}};
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<String,Integer> vv;
+    
+    VisualizationServer.Paintable rings;
+    
+    String root;
+    
+    TreeLayout<String,Integer> layout;
+    
+    BalloonLayout<String,Integer> radialLayout;
+    /**
+     * provides a Hyperbolic lens for the view
+     */
+    LensSupport hyperbolicViewSupport;
+    
+    public BalloonLayoutDemo() {
+        
+        // create a simple graph for the demo
+        graph = new DelegateForest<String,Integer>();
+
+        createTree();
+        
+        layout = new TreeLayout<String,Integer>(graph);
+        radialLayout = new BalloonLayout<String,Integer>(graph);
+        radialLayout.setSize(new Dimension(900,900));
+        vv =  new VisualizationViewer<String,Integer>(layout, new Dimension(600,600));
+        vv.setBackground(Color.white);
+        vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.quadCurve(graph));
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        vv.getRenderContext().setArrowFillPaintTransformer(Functions.<Paint>constant(Color.lightGray));
+        rings = new Rings(radialLayout);
+        
+        Container content = getContentPane();
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        content.add(panel);
+        
+        final DefaultModalGraphMouse<String, Integer> graphMouse =
+        		new DefaultModalGraphMouse<String, Integer>();
+
+        vv.setGraphMouse(graphMouse);
+        vv.addKeyListener(graphMouse.getModeKeyListener());
+        
+        hyperbolicViewSupport = 
+            new ViewLensSupport<String,Integer>(vv, new HyperbolicShapeTransformer(vv, 
+            		vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)), 
+                    new ModalLensGraphMouse());
+
+
+        graphMouse.addItemListener(hyperbolicViewSupport.getGraphMouse().getModeListener());
+
+        JComboBox<?> modeBox = graphMouse.getModeComboBox();
+        modeBox.addItemListener(graphMouse.getModeListener());
+        graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING);
+
+        final ScalingControl scaler = new CrossoverScalingControl();
+        
+        vv.scaleToLayout(scaler);
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        JToggleButton radial = new JToggleButton("Balloon");
+        radial.addItemListener(new ItemListener() {
+
+			public void itemStateChanged(ItemEvent e) {
+				if(e.getStateChange() == ItemEvent.SELECTED) {
+
+					LayoutTransition<String,Integer> lt =
+						new LayoutTransition<String,Integer>(vv, layout, radialLayout);
+					Animator animator = new Animator(lt);
+					animator.start();
+					vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).setToIdentity();
+					vv.addPreRenderPaintable(rings);
+				} else {
+
+					LayoutTransition<String,Integer> lt =
+						new LayoutTransition<String,Integer>(vv, radialLayout, layout);
+					Animator animator = new Animator(lt);
+					animator.start();
+					vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).setToIdentity();
+					vv.removePreRenderPaintable(rings);
+				}
+				vv.repaint();
+			}});
+        final JRadioButton hyperView = new JRadioButton("Hyperbolic View");
+        hyperView.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                hyperbolicViewSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
+            }
+        });
+
+        JPanel scaleGrid = new JPanel(new GridLayout(1,0));
+        scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom"));
+
+        JPanel controls = new JPanel();
+        scaleGrid.add(plus);
+        scaleGrid.add(minus);
+        controls.add(radial);
+        controls.add(scaleGrid);
+        controls.add(modeBox);
+        controls.add(hyperView);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+    
+    class Rings implements VisualizationServer.Paintable {
+    	
+    	BalloonLayout<String,Integer> layout;
+    	
+    	public Rings(BalloonLayout<String,Integer> layout) {
+    		this.layout = layout;
+    	}
+    	
+		public void paint(Graphics g) {
+			g.setColor(Color.gray);
+		
+			Graphics2D g2d = (Graphics2D)g;
+
+			Ellipse2D ellipse = new Ellipse2D.Double();
+			for(String v : layout.getGraph().getVertices()) {
+				Double radius = layout.getRadii().get(v);
+				if(radius == null) continue;
+				Point2D p = layout.apply(v);
+				ellipse.setFrame(-radius, -radius, 2*radius, 2*radius);
+				AffineTransform at = AffineTransform.getTranslateInstance(p.getX(), p.getY());
+				Shape shape = at.createTransformedShape(ellipse);
+				
+				MutableTransformer viewTransformer =
+					vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
+				
+				if(viewTransformer instanceof MutableTransformerDecorator) {
+					shape = vv.getRenderContext().getMultiLayerTransformer().transform(shape);
+				} else {
+					shape = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT,shape);
+				}
+
+				g2d.draw(shape);
+			}
+		}
+
+		public boolean useTransform() {
+			return true;
+		}
+    }
+    
+    /**
+     * 
+     */
+    private void createTree() {
+    	
+       	graph.addVertex("A0");
+       	graph.addEdge(edgeFactory.get(), "A0", "B0");
+       	graph.addEdge(edgeFactory.get(), "A0", "B1");
+       	graph.addEdge(edgeFactory.get(), "A0", "B2");
+       	
+       	graph.addEdge(edgeFactory.get(), "B0", "C0");
+       	graph.addEdge(edgeFactory.get(), "B0", "C1");
+       	graph.addEdge(edgeFactory.get(), "B0", "C2");
+       	graph.addEdge(edgeFactory.get(), "B0", "C3");
+
+       	graph.addEdge(edgeFactory.get(), "C2", "H0");
+       	graph.addEdge(edgeFactory.get(), "C2", "H1");
+
+       	graph.addEdge(edgeFactory.get(), "B1", "D0");
+       	graph.addEdge(edgeFactory.get(), "B1", "D1");
+       	graph.addEdge(edgeFactory.get(), "B1", "D2");
+
+       	graph.addEdge(edgeFactory.get(), "B2", "E0");
+       	graph.addEdge(edgeFactory.get(), "B2", "E1");
+       	graph.addEdge(edgeFactory.get(), "B2", "E2");
+
+       	graph.addEdge(edgeFactory.get(), "D0", "F0");
+       	graph.addEdge(edgeFactory.get(), "D0", "F1");
+       	graph.addEdge(edgeFactory.get(), "D0", "F2");
+       	
+       	graph.addEdge(edgeFactory.get(), "D1", "G0");
+       	graph.addEdge(edgeFactory.get(), "D1", "G1");
+       	graph.addEdge(edgeFactory.get(), "D1", "G2");
+       	graph.addEdge(edgeFactory.get(), "D1", "G3");
+       	graph.addEdge(edgeFactory.get(), "D1", "G4");
+       	graph.addEdge(edgeFactory.get(), "D1", "G5");
+       	graph.addEdge(edgeFactory.get(), "D1", "G6");
+       	graph.addEdge(edgeFactory.get(), "D1", "G7");
+       	
+       	// uncomment this to make it a Forest:
+//       	graph.addVertex("K0");
+//       	graph.addEdge(edgeFactory.get(), "K0", "K1");
+//       	graph.addEdge(edgeFactory.get(), "K0", "K2");
+//       	graph.addEdge(edgeFactory.get(), "K0", "K3");
+//       	
+//       	graph.addVertex("J0");
+//    	graph.addEdge(edgeFactory.get(), "J0", "J1");
+//    	graph.addEdge(edgeFactory.get(), "J0", "J2");
+//    	graph.addEdge(edgeFactory.get(), "J1", "J4");
+//    	graph.addEdge(edgeFactory.get(), "J2", "J3");
+////    	graph.addEdge(edgeFactory.get(), "J2", "J5");
+////    	graph.addEdge(edgeFactory.get(), "J4", "J6");
+////    	graph.addEdge(edgeFactory.get(), "J4", "J7");
+////    	graph.addEdge(edgeFactory.get(), "J3", "J8");
+////    	graph.addEdge(edgeFactory.get(), "J6", "B9");
+
+       	
+    }
+
+    public static void main(String[] args) {
+        JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+        content.add(new BalloonLayoutDemo());
+        frame.pack();
+        frame.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/ClusteringDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/ClusteringDemo.java
new file mode 100644
index 0000000..445b366
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/ClusteringDemo.java
@@ -0,0 +1,333 @@
+/*
+* Copyright (c) 2003, The JUNG Authors
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.samples;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.Paint;
+import java.awt.Stroke;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.Point2D;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+import javax.swing.JToggleButton;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import edu.uci.ics.jung.algorithms.cluster.EdgeBetweennessClusterer;
+import edu.uci.ics.jung.algorithms.layout.AggregateLayout;
+import edu.uci.ics.jung.algorithms.layout.CircleLayout;
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.util.Relaxer;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+import edu.uci.ics.jung.io.PajekNetReader;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+
+
+/**
+ * This simple app demonstrates how one can use our algorithms and visualization libraries in unison.
+ * In this case, we generate use the Zachary karate club data set, widely known in the social networks literature, then
+ * we cluster the vertices using an edge-betweenness clusterer, and finally we visualize the graph using
+ * Fruchtermain-Rheingold layout and provide a slider so that the user can adjust the clustering granularity.
+ * @author Scott White
+ */
+ at SuppressWarnings("serial")
+public class ClusteringDemo extends JApplet {
+
+	VisualizationViewer<Number,Number> vv;
+
+	LoadingCache<Number, Paint> vertexPaints =
+			CacheBuilder.newBuilder().build(
+					CacheLoader.from(Functions.<Paint>constant(Color.white))); 
+	LoadingCache<Number, Paint> edgePaints =
+			CacheBuilder.newBuilder().build(
+					CacheLoader.from(Functions.<Paint>constant(Color.blue))); 
+
+	public final Color[] similarColors =
+	{
+		new Color(216, 134, 134),
+		new Color(135, 137, 211),
+		new Color(134, 206, 189),
+		new Color(206, 176, 134),
+		new Color(194, 204, 134),
+		new Color(145, 214, 134),
+		new Color(133, 178, 209),
+		new Color(103, 148, 255),
+		new Color(60, 220, 220),
+		new Color(30, 250, 100)
+	};
+	
+	public static void main(String[] args) throws IOException {
+		
+		ClusteringDemo cd = new ClusteringDemo();
+		cd.start();
+		// Add a restart button so the graph can be redrawn to fit the size of the frame
+		JFrame jf = new JFrame();
+		jf.getContentPane().add(cd);
+		
+		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		jf.pack();
+		jf.setVisible(true);
+	}
+
+	public void start() {
+		InputStream is = this.getClass().getClassLoader().getResourceAsStream("datasets/zachary.net");
+		BufferedReader br = new BufferedReader( new InputStreamReader( is ));
+        
+        try
+        {
+            setUpView(br);
+        }
+        catch (IOException e)
+        {
+            System.out.println("Error in loading graph");
+            e.printStackTrace();
+        }
+	}
+
+	private void setUpView(BufferedReader br) throws IOException {
+		
+		Supplier<Number> vertexFactory = new Supplier<Number>() {
+            int n = 0;
+            public Number get() { return n++; }
+        };
+        Supplier<Number> edgeFactory = new Supplier<Number>()  {
+            int n = 0;
+            public Number get() { return n++; }
+        };
+
+        PajekNetReader<Graph<Number, Number>, Number,Number> pnr = 
+            new PajekNetReader<Graph<Number, Number>, Number,Number>(vertexFactory, edgeFactory);
+        
+        final Graph<Number,Number> graph = new SparseMultigraph<Number, Number>();
+        
+        pnr.load(br, graph);
+
+		//Create a simple layout frame
+        //specify the Fruchterman-Rheingold layout algorithm
+        final AggregateLayout<Number,Number> layout = 
+        	new AggregateLayout<Number,Number>(new FRLayout<Number,Number>(graph));
+
+		vv = new VisualizationViewer<Number,Number>(layout);
+		vv.setBackground( Color.white );
+		//Tell the renderer to use our own customized color rendering
+		vv.getRenderContext().setVertexFillPaintTransformer(vertexPaints);
+		vv.getRenderContext().setVertexDrawPaintTransformer(new Function<Number,Paint>() {
+			public Paint apply(Number v) {
+				if(vv.getPickedVertexState().isPicked(v)) {
+					return Color.cyan;
+				} else {
+					return Color.BLACK;
+				}
+			}
+		});
+
+		vv.getRenderContext().setEdgeDrawPaintTransformer(edgePaints);
+
+		vv.getRenderContext().setEdgeStrokeTransformer(new Function<Number,Stroke>() {
+                protected final Stroke THIN = new BasicStroke(1);
+                protected final Stroke THICK= new BasicStroke(2);
+                public Stroke apply(Number e)
+                {
+                    Paint c = edgePaints.getUnchecked(e);
+                    if (c == Color.LIGHT_GRAY)
+                        return THIN;
+                    else 
+                        return THICK;
+                }
+            });
+
+		//add restart button
+		JButton scramble = new JButton("Restart");
+		scramble.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent arg0) {
+				Layout<Number, Number> layout = vv.getGraphLayout();
+				layout.initialize();
+				Relaxer relaxer = vv.getModel().getRelaxer();
+				if(relaxer != null) {
+					relaxer.stop();
+					relaxer.prerelax();
+					relaxer.relax();
+				}
+			}
+
+		});
+		
+		DefaultModalGraphMouse<Number, Number> gm = new DefaultModalGraphMouse<Number, Number>();
+		vv.setGraphMouse(gm);
+		
+		final JToggleButton groupVertices = new JToggleButton("Group Clusters");
+
+		//Create slider to adjust the number of edges to remove when clustering
+		final JSlider edgeBetweennessSlider = new JSlider(JSlider.HORIZONTAL);
+        edgeBetweennessSlider.setBackground(Color.WHITE);
+		edgeBetweennessSlider.setPreferredSize(new Dimension(210, 50));
+		edgeBetweennessSlider.setPaintTicks(true);
+		edgeBetweennessSlider.setMaximum(graph.getEdgeCount());
+		edgeBetweennessSlider.setMinimum(0);
+		edgeBetweennessSlider.setValue(0);
+		edgeBetweennessSlider.setMajorTickSpacing(10);
+		edgeBetweennessSlider.setPaintLabels(true);
+		edgeBetweennessSlider.setPaintTicks(true);
+
+//		edgeBetweennessSlider.setBorder(BorderFactory.createLineBorder(Color.black));
+		//TO DO: edgeBetweennessSlider.add(new JLabel("Node Size (PageRank With Priors):"));
+		//I also want the slider value to appear
+		final JPanel eastControls = new JPanel();
+		eastControls.setOpaque(true);
+		eastControls.setLayout(new BoxLayout(eastControls, BoxLayout.Y_AXIS));
+		eastControls.add(Box.createVerticalGlue());
+		eastControls.add(edgeBetweennessSlider);
+
+		final String COMMANDSTRING = "Edges removed for clusters: ";
+		final String eastSize = COMMANDSTRING + edgeBetweennessSlider.getValue();
+		
+		final TitledBorder sliderBorder = BorderFactory.createTitledBorder(eastSize);
+		eastControls.setBorder(sliderBorder);
+		//eastControls.add(eastSize);
+		eastControls.add(Box.createVerticalGlue());
+		
+		groupVertices.addItemListener(new ItemListener() {
+			public void itemStateChanged(ItemEvent e) {
+					clusterAndRecolor(layout, edgeBetweennessSlider.getValue(), 
+							similarColors, e.getStateChange() == ItemEvent.SELECTED);
+					vv.repaint();
+			}});
+
+
+		clusterAndRecolor(layout, 0, similarColors, groupVertices.isSelected());
+
+		edgeBetweennessSlider.addChangeListener(new ChangeListener() {
+			public void stateChanged(ChangeEvent e) {
+				JSlider source = (JSlider) e.getSource();
+				if (!source.getValueIsAdjusting()) {
+					int numEdgesToRemove = source.getValue();
+					clusterAndRecolor(layout, numEdgesToRemove, similarColors,
+							groupVertices.isSelected());
+					sliderBorder.setTitle(
+						COMMANDSTRING + edgeBetweennessSlider.getValue());
+					eastControls.repaint();
+					vv.validate();
+					vv.repaint();
+				}
+			}
+		});
+
+		Container content = getContentPane();
+		content.add(new GraphZoomScrollPane(vv));
+		JPanel south = new JPanel();
+		JPanel grid = new JPanel(new GridLayout(2,1));
+		grid.add(scramble);
+		grid.add(groupVertices);
+		south.add(grid);
+		south.add(eastControls);
+		JPanel p = new JPanel();
+		p.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
+		p.add(gm.getModeComboBox());
+		south.add(p);
+		content.add(south, BorderLayout.SOUTH);
+	}
+
+	public void clusterAndRecolor(AggregateLayout<Number,Number> layout,
+		int numEdgesToRemove,
+		Color[] colors, boolean groupClusters) {
+		//Now cluster the vertices by removing the top 50 edges with highest betweenness
+		//		if (numEdgesToRemove == 0) {
+		//			colorCluster( g.getVertices(), colors[0] );
+		//		} else {
+		
+		Graph<Number,Number> g = layout.getGraph();
+        layout.removeAll();
+
+		EdgeBetweennessClusterer<Number,Number> clusterer =
+			new EdgeBetweennessClusterer<Number,Number>(numEdgesToRemove);
+		Set<Set<Number>> clusterSet = clusterer.apply(g);
+		List<Number> edges = clusterer.getEdgesRemoved();
+
+		int i = 0;
+		//Set the colors of each node so that each cluster's vertices have the same color
+		for (Iterator<Set<Number>> cIt = clusterSet.iterator(); cIt.hasNext();) {
+
+			Set<Number> vertices = cIt.next();
+			Color c = colors[i % colors.length];
+
+			colorCluster(vertices, c);
+			if(groupClusters == true) {
+				groupCluster(layout, vertices);
+			}
+			i++;
+		}
+		for (Number e : g.getEdges()) {
+
+			if (edges.contains(e)) {
+				edgePaints.put(e, Color.lightGray);
+			} else {
+				edgePaints.put(e, Color.black);
+			}
+		}
+
+	}
+
+	private void colorCluster(Set<Number> vertices, Color c) {
+		for (Number v : vertices) {
+			vertexPaints.put(v, c);
+		}
+	}
+	
+	private void groupCluster(AggregateLayout<Number,Number> layout, Set<Number> vertices) {
+		if(vertices.size() < layout.getGraph().getVertexCount()) {
+			Point2D center = layout.apply(vertices.iterator().next());
+			Graph<Number,Number> subGraph = SparseMultigraph.<Number,Number>getFactory().get();
+			for(Number v : vertices) {
+				subGraph.addVertex(v);
+			}
+			Layout<Number,Number> subLayout = 
+				new CircleLayout<Number,Number>(subGraph);
+			subLayout.setInitializer(vv.getGraphLayout());
+			subLayout.setSize(new Dimension(40,40));
+
+			layout.put(subLayout,center);
+			vv.repaint();
+		}
+	}
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/DemoLensVertexImageShaperDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/DemoLensVertexImageShaperDemo.java
new file mode 100644
index 0000000..e832478
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/DemoLensVertexImageShaperDemo.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.GridLayout;
+import java.awt.Paint;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.graph.DirectedSparseGraph;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.LayeredIcon;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.decorators.VertexIconShapeTransformer;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.renderers.Checkmark;
+import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer;
+import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer;
+import edu.uci.ics.jung.visualization.transform.LayoutLensSupport;
+import edu.uci.ics.jung.visualization.transform.LensSupport;
+import edu.uci.ics.jung.visualization.transform.shape.MagnifyImageLensSupport;
+
+
+/**
+ * Demonstrates the use of images to represent graph vertices.
+ * The images are added to the DefaultGraphLabelRenderer and can
+ * either be offset from the vertex, or centered on the vertex.
+ * Additionally, the relative positioning of the label and
+ * image is controlled by subclassing the DefaultGraphLabelRenderer
+ * and setting the appropriate properties on its JLabel superclass
+ *  FancyGraphLabelRenderer
+ * 
+ * The images used in this demo (courtesy of slashdot.org) are
+ * rectangular but with a transparent background. When vertices
+ * are represented by these images, it looks better if the actual
+ * shape of the opaque part of the image is computed so that the
+ * edge arrowheads follow the visual shape of the image. This demo
+ * uses the FourPassImageShaper class to compute the Shape from
+ * an image with transparent background.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+public class DemoLensVertexImageShaperDemo extends JApplet {
+
+	   /**
+	 * 
+	 */
+	private static final long serialVersionUID = 5432239991020505763L;
+
+	/**
+     * the graph
+     */
+    DirectedSparseGraph<Number, Number> graph;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<Number,Number> vv;
+    
+    /**
+     * some icon names to use
+     */
+    String[] iconNames = {
+            "sample2"
+    };
+    
+    LensSupport viewSupport;
+    LensSupport modelSupport;
+    LensSupport magnifyLayoutSupport;
+    LensSupport magnifyViewSupport;
+    /**
+     * create an instance of a simple graph with controls to
+     * demo the zoom features.
+     * 
+     */
+    public DemoLensVertexImageShaperDemo() {
+        
+        // create a simple graph for the demo
+        graph = new DirectedSparseGraph<Number,Number>();
+        Number[] vertices = createVertices(1);
+        
+        // a Map for the labels
+        Map<Number,String> map = new HashMap<Number,String>();
+        for(int i=0; i<vertices.length; i++) {
+            map.put(vertices[i], iconNames[i%iconNames.length]);
+        }
+        
+        // a Map for the Icons
+        Map<Number,Icon> iconMap = new HashMap<Number,Icon>();
+        for(int i=0; i<vertices.length; i++) {
+            String name = "/images/topic"+iconNames[i]+".gif";
+            try {
+                Icon icon = 
+                    new LayeredIcon(new ImageIcon(DemoLensVertexImageShaperDemo.class.getResource(name)).getImage());
+                iconMap.put(vertices[i], icon);
+            } catch(Exception ex) {
+                System.err.println("You need slashdoticons.jar in your classpath to see the image "+name);
+            }
+        }
+        
+        createEdges(vertices);
+        
+        FRLayout<Number, Number> layout = new FRLayout<Number, Number>(graph);
+        layout.setMaxIterations(100);
+        vv =  new VisualizationViewer<Number, Number>(layout, new Dimension(600,600));
+        
+        Function<Number,Paint> vpf = 
+            new PickableVertexPaintTransformer<Number>(vv.getPickedVertexState(), Color.white, Color.yellow);
+        vv.getRenderContext().setVertexFillPaintTransformer(vpf);
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv.getPickedEdgeState(), Color.black, Color.cyan));
+
+        vv.setBackground(Color.white);
+        
+        final Function<Number,String> vertexStringerImpl = 
+            new VertexStringerImpl<Number>(map);
+        vv.getRenderContext().setVertexLabelTransformer(vertexStringerImpl);
+        vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.cyan));
+        vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.cyan));
+        
+        
+        // features on and off. For a real application, use VertexIconAndShapeFunction instead.
+        final VertexIconShapeTransformer<Number> vertexImageShapeFunction =
+            new VertexIconShapeTransformer<Number>(new EllipseVertexShapeTransformer<Number>());
+        vertexImageShapeFunction.setIconMap(iconMap);
+
+        final Function<Number, Icon> vertexIconFunction = Functions.forMap(iconMap);
+        
+        vv.getRenderContext().setVertexShapeTransformer(vertexImageShapeFunction);
+        vv.getRenderContext().setVertexIconTransformer(vertexIconFunction);
+        
+        // Get the pickedState and add a listener that will decorate the
+        // Vertex images with a checkmark icon when they are picked
+        PickedState<Number> ps = vv.getPickedVertexState();
+        ps.addItemListener(new PickWithIconListener(vertexIconFunction));
+        
+        vv.addPostRenderPaintable(new VisualizationViewer.Paintable(){
+            int x;
+            int y;
+            Font font;
+            FontMetrics metrics;
+            int swidth;
+            int sheight;
+            String str = "Thank You, slashdot.org, for the images!";
+            
+            public void paint(Graphics g) {
+                Dimension d = vv.getSize();
+                if(font == null) {
+                    font = new Font(g.getFont().getName(), Font.BOLD, 20);
+                    metrics = g.getFontMetrics(font);
+                    swidth = metrics.stringWidth(str);
+                    sheight = metrics.getMaxAscent()+metrics.getMaxDescent();
+                    x = (d.width-swidth)/2;
+                    y = (int)(d.height-sheight*1.5);
+                }
+                g.setFont(font);
+                Color oldColor = g.getColor();
+                g.setColor(Color.lightGray);
+                g.drawString(str, x, y);
+                g.setColor(oldColor);
+            }
+            public boolean useTransform() {
+                return false;
+            }
+        });
+
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        Container content = getContentPane();
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        content.add(panel);
+        
+        final DefaultModalGraphMouse<Number, Number> graphMouse
+        	= new DefaultModalGraphMouse<Number, Number>();
+        vv.setGraphMouse(graphMouse);
+        
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        JComboBox<Mode> modeBox = graphMouse.getModeComboBox();
+        JPanel modePanel = new JPanel();
+        modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
+        modePanel.add(modeBox);
+        
+        JPanel scaleGrid = new JPanel(new GridLayout(1,0));
+        scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        JPanel controls = new JPanel();
+        scaleGrid.add(plus);
+        scaleGrid.add(minus);
+        controls.add(scaleGrid);
+
+        controls.add(modePanel);
+        content.add(controls, BorderLayout.SOUTH);
+        
+        this.viewSupport = new MagnifyImageLensSupport<Number,Number>(vv);
+//        	new ViewLensSupport<Number,Number>(vv, new HyperbolicShapeTransformer(vv, 
+//        		vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)), 
+//                new ModalLensGraphMouse());
+
+        this.modelSupport = new LayoutLensSupport<Number,Number>(vv);
+        
+        graphMouse.addItemListener(modelSupport.getGraphMouse().getModeListener());
+        graphMouse.addItemListener(viewSupport.getGraphMouse().getModeListener());
+
+        ButtonGroup radio = new ButtonGroup();
+        JRadioButton none = new JRadioButton("None");
+        none.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                if(viewSupport != null) {
+                    viewSupport.deactivate();
+                }
+                if(modelSupport != null) {
+                    modelSupport.deactivate();
+                }
+            }
+        });
+        none.setSelected(true);
+
+        JRadioButton hyperView = new JRadioButton("View");
+        hyperView.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                viewSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
+            }
+        });
+
+        JRadioButton hyperModel = new JRadioButton("Layout");
+        hyperModel.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                modelSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
+            }
+        });
+        radio.add(none);
+        radio.add(hyperView);
+        radio.add(hyperModel);
+        
+        JMenuBar menubar = new JMenuBar();
+        JMenu modeMenu = graphMouse.getModeMenu();
+        menubar.add(modeMenu);
+
+        JPanel lensPanel = new JPanel(new GridLayout(2,0));
+        lensPanel.setBorder(BorderFactory.createTitledBorder("Lens"));
+        lensPanel.add(none);
+        lensPanel.add(hyperView);
+        lensPanel.add(hyperModel);
+        controls.add(lensPanel);
+    }
+    
+    /**
+     * A simple implementation of VertexStringer that
+     * gets Vertex labels from a Map  
+     * 
+     * @author Tom Nelson 
+     *
+     *
+     */
+    class VertexStringerImpl<V> implements Function<V,String> {
+
+        Map<V,String> map = new HashMap<V,String>();
+        
+        boolean enabled = true;
+        
+        public VertexStringerImpl(Map<V,String> map) {
+            this.map = map;
+        }
+        
+        /**
+         * @see edu.uci.ics.jung.graph.decorators.VertexStringer#getLabel(edu.uci.ics.jung.graph.Vertex)
+         */
+        public String apply(V v) {
+            if(isEnabled()) {
+                return map.get(v);
+            } else {
+                return "";
+            }
+        }
+
+        /**
+         * @return Returns the enabled.
+         */
+        public boolean isEnabled() {
+            return enabled;
+        }
+
+        /**
+         * @param enabled The enabled to set.
+         */
+        public void setEnabled(boolean enabled) {
+            this.enabled = enabled;
+        }
+    }
+    
+    /**
+     * create some vertices
+     * @param count how many to create
+     * @return the Vertices in an array
+     */
+    private Number[] createVertices(int count) {
+        Number[] v = new Number[count];
+        for (int i = 0; i < count; i++) {
+            v[i] = new Integer(i);
+            graph.addVertex(v[i]);
+        }
+        return v;
+    }
+
+    /**
+     * create edges for this demo graph
+     * @param v an array of Vertices to connect
+     */
+    void createEdges(Number[] v) {
+//        graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[3], v[0], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[0], v[4], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[4], v[5], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[5], v[3], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[2], v[1], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[4], v[1], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[8], v[2], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[3], v[8], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[6], v[7], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[7], v[5], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[0], v[9], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[9], v[8], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[7], v[6], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[6], v[5], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[4], v[2], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[5], v[4], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[4], v[10], EdgeType.DIRECTED);
+//        graph.addEdge(new Double(Math.random()), v[10], v[4], EdgeType.DIRECTED);
+    }
+    
+    public static class PickWithIconListener implements ItemListener {
+        Function<Number, Icon> imager;
+        Icon checked;
+        
+        public PickWithIconListener(Function<Number, Icon> imager) {
+            this.imager = imager;
+            checked = new Checkmark(Color.red);
+        }
+
+        public void itemStateChanged(ItemEvent e) {
+            Icon icon = imager.apply((Number)e.getItem());
+            if(icon != null && icon instanceof LayeredIcon) {
+                if(e.getStateChange() == ItemEvent.SELECTED) {
+                    ((LayeredIcon)icon).add(checked);
+                } else {
+                    ((LayeredIcon)icon).remove(checked);
+                }
+            }
+        }
+    }
+
+
+    public static void main(String[] args) {
+        JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+        content.add(new DemoLensVertexImageShaperDemo());
+        frame.pack();
+        frame.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/DrawnIconVertexDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/DrawnIconVertexDemo.java
new file mode 100644
index 0000000..0d3cad2
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/DrawnIconVertexDemo.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.graph.DirectedSparseGraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer;
+import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer;
+
+/**
+ * A demo that shows drawn Icons as vertices
+ * 
+ * @author Tom Nelson 
+ * 
+ */
+public class DrawnIconVertexDemo {
+
+    /**
+     * the graph
+     */
+    Graph<Integer,Number> graph;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<Integer,Number> vv;
+    
+    public DrawnIconVertexDemo() {
+        
+        // create a simple graph for the demo
+        graph = new DirectedSparseGraph<Integer,Number>();
+        Integer[] v = createVertices(10);
+        createEdges(v);
+        
+        vv =  new VisualizationViewer<Integer,Number>(new FRLayout<Integer,Number>(graph));
+        vv.getRenderContext().setVertexLabelTransformer(new Function<Integer,String>(){
+
+			public String apply(Integer v) {
+				return "Vertex "+v;
+			}});
+        vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.cyan));
+        vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.cyan));
+
+        vv.getRenderContext().setVertexIconTransformer(new Function<Integer,Icon>() {
+
+        	/*
+        	 * Implements the Icon interface to draw an Icon with background color and
+        	 * a text label
+        	 */
+			public Icon apply(final Integer v) {
+				return new Icon() {
+
+					public int getIconHeight() {
+						return 20;
+					}
+
+					public int getIconWidth() {
+						return 20;
+					}
+
+					public void paintIcon(Component c, Graphics g,
+							int x, int y) {
+						if(vv.getPickedVertexState().isPicked(v)) {
+							g.setColor(Color.yellow);
+						} else {
+							g.setColor(Color.red);
+						}
+						g.fillOval(x, y, 20, 20);
+						if(vv.getPickedVertexState().isPicked(v)) {
+							g.setColor(Color.black);
+						} else {
+							g.setColor(Color.white);
+						}
+						g.drawString(""+v, x+6, y+15);
+						
+					}};
+			}});
+
+        vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<Integer>(vv.getPickedVertexState(), Color.white,  Color.yellow));
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv.getPickedEdgeState(), Color.black, Color.lightGray));
+
+        vv.setBackground(Color.white);
+
+        // add my listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        // create a frome to hold the graph
+        final JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        content.add(panel);
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        
+        final DefaultModalGraphMouse<Integer, Number> gm
+        	= new DefaultModalGraphMouse<Integer,Number>();
+        vv.setGraphMouse(gm);
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+
+        JPanel controls = new JPanel();
+        controls.add(plus);
+        controls.add(minus);
+        controls.add(gm.getModeComboBox());
+        content.add(controls, BorderLayout.SOUTH);
+
+        frame.pack();
+        frame.setVisible(true);
+    }
+    
+    
+    /**
+     * create some vertices
+     * @param count how many to create
+     * @return the Vertices in an array
+     */
+    private Integer[] createVertices(int count) {
+        Integer[] v = new Integer[count];
+        for (int i = 0; i < count; i++) {
+            v[i] = new Integer(i);
+            graph.addVertex(v[i]);
+        }
+        return v;
+    }
+
+    /**
+     * create edges for this demo graph
+     * @param v an array of Vertices to connect
+     */
+    void createEdges(Integer[] v) {
+        graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[3], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[4], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[4], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[3], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[1], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[1], v[4], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[8], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[3], v[8], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[6], v[7], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[7], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[9], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[9], v[8], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[7], v[6], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[6], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[4], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[5], v[4], EdgeType.DIRECTED);
+    }
+
+    public static void main(String[] args) 
+    {
+        new DrawnIconVertexDemo();
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/EdgeLabelDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/EdgeLabelDemo.java
new file mode 100644
index 0000000..e0010de
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/EdgeLabelDemo.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.AbstractButton;
+import javax.swing.BorderFactory;
+import javax.swing.BoundedRangeModel;
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.DefaultBoundedRangeModel;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JSlider;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.layout.CircleLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.ParallelEdgeShapeTransformer;
+import edu.uci.ics.jung.visualization.decorators.ConstantDirectionalEdgeValueTransformer;
+import edu.uci.ics.jung.visualization.decorators.EdgeShape;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.renderers.EdgeLabelRenderer;
+import edu.uci.ics.jung.visualization.renderers.VertexLabelRenderer;
+
+/**
+ * Demonstrates jung support for drawing edge labels that
+ * can be positioned at any point along the edge, and can
+ * be rotated to be parallel with the edge.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+public class EdgeLabelDemo extends JApplet {
+	private static final long serialVersionUID = -6077157664507049647L;
+
+	/**
+     * the graph
+     */
+    Graph<Integer,Number> graph;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<Integer,Number> vv;
+    
+    /**
+     */
+    VertexLabelRenderer vertexLabelRenderer;
+    EdgeLabelRenderer edgeLabelRenderer;
+    
+    ScalingControl scaler = new CrossoverScalingControl();
+    
+    /**
+     * create an instance of a simple graph with controls to
+     * demo the label positioning features
+     * 
+     */
+    @SuppressWarnings("serial")
+	public EdgeLabelDemo() {
+        
+        // create a simple graph for the demo
+        graph = new SparseMultigraph<Integer,Number>();
+        Integer[] v = createVertices(3);
+        createEdges(v);
+        
+        Layout<Integer,Number> layout = new CircleLayout<Integer,Number>(graph);
+        vv =  new VisualizationViewer<Integer,Number>(layout, new Dimension(600,400));
+        vv.setBackground(Color.white);
+
+        vertexLabelRenderer = vv.getRenderContext().getVertexLabelRenderer();
+        edgeLabelRenderer = vv.getRenderContext().getEdgeLabelRenderer();
+        
+        Function<Number,String> stringer = new Function<Number,String>(){
+            public String apply(Number e) {
+                return "Edge:"+graph.getEndpoints(e).toString();
+            }
+        };
+        vv.getRenderContext().setEdgeLabelTransformer(stringer);
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv.getPickedEdgeState(), Color.black, Color.cyan));
+        vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<Integer>(vv.getPickedVertexState(), Color.red, Color.yellow));
+        // add my listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        // create a frome to hold the graph
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        Container content = getContentPane();
+        content.add(panel);
+        
+        final DefaultModalGraphMouse<Integer,Number> graphMouse = new DefaultModalGraphMouse<Integer,Number>();
+        vv.setGraphMouse(graphMouse);
+        
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        ButtonGroup radio = new ButtonGroup();
+        JRadioButton lineButton = new JRadioButton("Line");
+        lineButton.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                if(e.getStateChange() == ItemEvent.SELECTED) {
+                    vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph));
+                    vv.repaint();
+                }
+            }
+        });
+        
+        JRadioButton quadButton = new JRadioButton("QuadCurve");
+        quadButton.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                if(e.getStateChange() == ItemEvent.SELECTED) {
+                    vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.quadCurve(graph));
+                    vv.repaint();
+                }
+            }
+        });
+        
+        JRadioButton cubicButton = new JRadioButton("CubicCurve");
+        cubicButton.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                if(e.getStateChange() == ItemEvent.SELECTED) {
+                    vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.cubicCurve(graph));
+                    vv.repaint();
+                }
+            }
+        });
+        radio.add(lineButton);
+        radio.add(quadButton);
+        radio.add(cubicButton);
+
+        graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING);
+        
+        JCheckBox rotate = new JCheckBox("<html><center>EdgeType<p>Parallel</center></html>");
+        rotate.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                AbstractButton b = (AbstractButton)e.getSource();
+                edgeLabelRenderer.setRotateEdgeLabels(b.isSelected());
+                vv.repaint();
+            }
+        });
+        rotate.setSelected(true);
+        MutableDirectionalEdgeValue mv = new MutableDirectionalEdgeValue(.5, .7);
+        vv.getRenderContext().setEdgeLabelClosenessTransformer(mv);
+        JSlider directedSlider = new JSlider(mv.getDirectedModel()) {
+            public Dimension getPreferredSize() {
+                Dimension d = super.getPreferredSize();
+                d.width /= 2;
+                return d;
+            }
+        };
+        JSlider undirectedSlider = new JSlider(mv.getUndirectedModel()) {
+            public Dimension getPreferredSize() {
+                Dimension d = super.getPreferredSize();
+                d.width /= 2;
+                return d;
+            }
+        };
+        
+        JSlider edgeOffsetSlider = new JSlider(0,50) {
+            public Dimension getPreferredSize() {
+                Dimension d = super.getPreferredSize();
+                d.width /= 2;
+                return d;
+            }
+        };
+        edgeOffsetSlider.addChangeListener(new ChangeListener() {
+            @SuppressWarnings("rawtypes")
+			public void stateChanged(ChangeEvent e) {
+                JSlider s = (JSlider)e.getSource();
+                Function<? super Number, Shape> edgeShapeFunction
+                	= vv.getRenderContext().getEdgeShapeTransformer();
+                if (edgeShapeFunction instanceof ParallelEdgeShapeTransformer) {
+                	((ParallelEdgeShapeTransformer)edgeShapeFunction)
+                		.setControlOffsetIncrement(s.getValue());
+                	vv.repaint();
+                }
+            }
+        });
+        
+        Box controls = Box.createHorizontalBox();
+
+        JPanel zoomPanel = new JPanel(new GridLayout(0,1));
+        zoomPanel.setBorder(BorderFactory.createTitledBorder("Scale"));
+        zoomPanel.add(plus);
+        zoomPanel.add(minus);
+
+        JPanel edgePanel = new JPanel(new GridLayout(0,1));
+        edgePanel.setBorder(BorderFactory.createTitledBorder("EdgeType Type"));
+        edgePanel.add(lineButton);
+        edgePanel.add(quadButton);
+        edgePanel.add(cubicButton);
+
+        JPanel rotatePanel = new JPanel();
+        rotatePanel.setBorder(BorderFactory.createTitledBorder("Alignment"));
+        rotatePanel.add(rotate);
+
+        JPanel labelPanel = new JPanel(new BorderLayout());
+        JPanel sliderPanel = new JPanel(new GridLayout(3,1));
+        JPanel sliderLabelPanel = new JPanel(new GridLayout(3,1));
+        JPanel offsetPanel = new JPanel(new BorderLayout());
+        offsetPanel.setBorder(BorderFactory.createTitledBorder("Offset"));
+        sliderPanel.add(directedSlider);
+        sliderPanel.add(undirectedSlider);
+        sliderPanel.add(edgeOffsetSlider);
+        sliderLabelPanel.add(new JLabel("Directed", JLabel.RIGHT));
+        sliderLabelPanel.add(new JLabel("Undirected", JLabel.RIGHT));
+        sliderLabelPanel.add(new JLabel("Edges", JLabel.RIGHT));
+        offsetPanel.add(sliderLabelPanel, BorderLayout.WEST);
+        offsetPanel.add(sliderPanel);
+        labelPanel.add(offsetPanel);
+        labelPanel.add(rotatePanel, BorderLayout.WEST);
+        
+        JPanel modePanel = new JPanel(new GridLayout(2,1));
+        modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
+        modePanel.add(graphMouse.getModeComboBox());
+
+        controls.add(zoomPanel);
+        controls.add(edgePanel);
+        controls.add(labelPanel);
+        controls.add(modePanel);
+        content.add(controls, BorderLayout.SOUTH);
+        quadButton.setSelected(true);
+    }
+    
+    /**
+     * subclassed to hold two BoundedRangeModel instances that
+     * are used by JSliders to move the edge label positions
+     * @author Tom Nelson
+     *
+     *
+     */
+    class MutableDirectionalEdgeValue extends ConstantDirectionalEdgeValueTransformer<Integer,Number> {
+        BoundedRangeModel undirectedModel = new DefaultBoundedRangeModel(5,0,0,10);
+        BoundedRangeModel directedModel = new DefaultBoundedRangeModel(7,0,0,10);
+        
+        public MutableDirectionalEdgeValue(double undirected, double directed) {
+            super(undirected, directed);
+            undirectedModel.setValue((int)(undirected*10));
+            directedModel.setValue((int)(directed*10));
+            
+            undirectedModel.addChangeListener(new ChangeListener(){
+                public void stateChanged(ChangeEvent e) {
+                    setUndirectedValue(new Double(undirectedModel.getValue()/10f));
+                    vv.repaint();
+                }
+            });
+            directedModel.addChangeListener(new ChangeListener(){
+                public void stateChanged(ChangeEvent e) {
+                    setDirectedValue(new Double(directedModel.getValue()/10f));
+                    vv.repaint();
+                }
+            });
+        }
+        /**
+         * @return Returns the directedModel.
+         */
+        public BoundedRangeModel getDirectedModel() {
+            return directedModel;
+        }
+
+        /**
+         * @return Returns the undirectedModel.
+         */
+        public BoundedRangeModel getUndirectedModel() {
+            return undirectedModel;
+        }
+    }
+    
+    /**
+     * create some vertices
+     * @param count how many to create
+     * @return the Vertices in an array
+     */
+    private Integer[] createVertices(int count) {
+        Integer[] v = new Integer[count];
+        for (int i = 0; i < count; i++) {
+            v[i] = new Integer(i);
+            graph.addVertex(v[i]);
+        }
+        return v;
+    }
+
+    /**
+     * create edges for this demo graph
+     * @param v an array of Vertices to connect
+     */
+    void createEdges(Integer[] v) {
+        graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[1], v[0], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[1], v[0], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[1], v[2]);
+        graph.addEdge(new Double(Math.random()), v[1], v[2]);
+    }
+
+    public static void main(String[] args) {
+        JFrame frame = new JFrame();
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        Container content = frame.getContentPane();
+        content.add(new EdgeLabelDemo());
+        frame.pack();
+        frame.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/GraphEditorDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/GraphEditorDemo.java
new file mode 100644
index 0000000..cab6dad
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/GraphEditorDemo.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
+import java.awt.print.Printable;
+import java.awt.print.PrinterJob;
+import java.io.File;
+
+import javax.imageio.ImageIO;
+import javax.swing.AbstractAction;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.layout.AbstractLayout;
+import edu.uci.ics.jung.algorithms.layout.StaticLayout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.annotations.AnnotationControls;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.EditingModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+
+/**
+ * Shows how  to create a graph editor with JUNG.
+ * Mouse modes and actions are explained in the help text.
+ * The application version of GraphEditorDemo provides a
+ * File menu with an option to save the visible graph as
+ * a jpeg file.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+public class GraphEditorDemo extends JApplet implements Printable {
+
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = -2023243689258876709L;
+
+	/**
+     * the graph
+     */
+    Graph<Number,Number> graph;
+    
+    AbstractLayout<Number,Number> layout;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<Number,Number> vv;
+    
+    String instructions =
+        "<html>"+
+        "<h3>All Modes:</h3>"+
+        "<ul>"+
+        "<li>Right-click an empty area for <b>Create Vertex</b> popup"+
+        "<li>Right-click on a Vertex for <b>Delete Vertex</b> popup"+
+        "<li>Right-click on a Vertex for <b>Add Edge</b> menus <br>(if there are selected Vertices)"+
+        "<li>Right-click on an Edge for <b>Delete Edge</b> popup"+
+        "<li>Mousewheel scales with a crossover value of 1.0.<p>"+
+        "     - scales the graph layout when the combined scale is greater than 1<p>"+
+        "     - scales the graph view when the combined scale is less than 1"+
+
+        "</ul>"+
+        "<h3>Editing Mode:</h3>"+
+        "<ul>"+
+        "<li>Left-click an empty area to create a new Vertex"+
+        "<li>Left-click on a Vertex and drag to another Vertex to create an Undirected Edge"+
+        "<li>Shift+Left-click on a Vertex and drag to another Vertex to create a Directed Edge"+
+        "</ul>"+
+        "<h3>Picking Mode:</h3>"+
+        "<ul>"+
+        "<li>Mouse1 on a Vertex selects the vertex"+
+        "<li>Mouse1 elsewhere unselects all Vertices"+
+        "<li>Mouse1+Shift on a Vertex adds/removes Vertex selection"+
+        "<li>Mouse1+drag on a Vertex moves all selected Vertices"+
+        "<li>Mouse1+drag elsewhere selects Vertices in a region"+
+        "<li>Mouse1+Shift+drag adds selection of Vertices in a new region"+
+        "<li>Mouse1+CTRL on a Vertex selects the vertex and centers the display on it"+
+        "<li>Mouse1 double-click on a vertex or edge allows you to edit the label"+
+        "</ul>"+
+        "<h3>Transforming Mode:</h3>"+
+        "<ul>"+
+        "<li>Mouse1+drag pans the graph"+
+        "<li>Mouse1+Shift+drag rotates the graph"+
+        "<li>Mouse1+CTRL(or Command)+drag shears the graph"+
+        "<li>Mouse1 double-click on a vertex or edge allows you to edit the label"+
+        "</ul>"+
+        "<h3>Annotation Mode:</h3>"+
+        "<ul>"+
+        "<li>Mouse1 begins drawing of a Rectangle"+
+        "<li>Mouse1+drag defines the Rectangle shape"+
+        "<li>Mouse1 release adds the Rectangle as an annotation"+
+        "<li>Mouse1+Shift begins drawing of an Ellipse"+
+        "<li>Mouse1+Shift+drag defines the Ellipse shape"+
+        "<li>Mouse1+Shift release adds the Ellipse as an annotation"+
+        "<li>Mouse3 shows a popup to input text, which will become"+
+        "<li>a text annotation on the graph at the mouse location"+
+        "</ul>"+
+        "</html>";
+    
+    /**
+     * create an instance of a simple graph with popup controls to
+     * create a graph.
+     * 
+     */
+    public GraphEditorDemo() {
+        
+        // create a simple graph for the demo
+        graph = new SparseMultigraph<Number,Number>();
+
+        this.layout = new StaticLayout<Number,Number>(graph, 
+        	new Dimension(600,600));
+        
+        vv =  new VisualizationViewer<Number,Number>(layout);
+        vv.setBackground(Color.white);
+
+        Function<Object, String> labeller = new ToStringLabeller();
+        vv.getRenderContext().setVertexLabelTransformer(labeller);
+        vv.getRenderContext().setEdgeLabelTransformer(labeller);
+
+        vv.setVertexToolTipTransformer(vv.getRenderContext().getVertexLabelTransformer());
+        
+
+        Container content = getContentPane();
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        content.add(panel);
+        Supplier<Number> vertexFactory = new VertexFactory();
+        Supplier<Number> edgeFactory = new EdgeFactory();
+        
+        final EditingModalGraphMouse<Number,Number> graphMouse = 
+        	new EditingModalGraphMouse<Number,Number>(vv.getRenderContext(), vertexFactory, edgeFactory);
+        
+        // the EditingGraphMouse will pass mouse event coordinates to the
+        // vertexLocations function to set the locations of the vertices as
+        // they are created
+//        graphMouse.setVertexLocations(vertexLocations);
+        vv.setGraphMouse(graphMouse);
+        vv.addKeyListener(graphMouse.getModeKeyListener());
+
+        graphMouse.setMode(ModalGraphMouse.Mode.EDITING);
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        JButton help = new JButton("Help");
+        help.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                JOptionPane.showMessageDialog(vv, instructions);
+            }});
+
+        AnnotationControls<Number,Number> annotationControls = 
+        	new AnnotationControls<Number,Number>(graphMouse.getAnnotatingPlugin());
+        JPanel controls = new JPanel();
+        controls.add(plus);
+        controls.add(minus);
+        JComboBox<Mode> modeBox = graphMouse.getModeComboBox();
+        controls.add(modeBox);
+        controls.add(annotationControls.getAnnotationsToolBar());
+        controls.add(help);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+    
+    /**
+     * copy the visible part of the graph to a file as a jpeg image
+     * @param file the file in which to save the graph image
+     */
+    public void writeJPEGImage(File file) {
+        int width = vv.getWidth();
+        int height = vv.getHeight();
+
+        BufferedImage bi = new BufferedImage(width, height,
+                BufferedImage.TYPE_INT_RGB);
+        Graphics2D graphics = bi.createGraphics();
+        vv.paint(graphics);
+        graphics.dispose();
+        
+        try {
+            ImageIO.write(bi, "jpeg", file);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+    
+    public int print(java.awt.Graphics graphics,
+            java.awt.print.PageFormat pageFormat, int pageIndex)
+            throws java.awt.print.PrinterException {
+        if (pageIndex > 0) {
+            return (Printable.NO_SUCH_PAGE);
+        } else {
+            java.awt.Graphics2D g2d = (java.awt.Graphics2D) graphics;
+            vv.setDoubleBuffered(false);
+            g2d.translate(pageFormat.getImageableX(), pageFormat
+                    .getImageableY());
+
+            vv.paint(g2d);
+            vv.setDoubleBuffered(true);
+
+            return (Printable.PAGE_EXISTS);
+        }
+    }
+    
+    class VertexFactory implements Supplier<Number> {
+
+    	int i=0;
+
+		public Number get() {
+			return i++;
+		}
+    }
+    
+    class EdgeFactory implements Supplier<Number> {
+
+    	int i=0;
+    	
+		public Number get() {
+			return i++;
+		}
+    }
+
+    @SuppressWarnings("serial")
+	public static void main(String[] args) {
+        JFrame frame = new JFrame();
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        final GraphEditorDemo demo = new GraphEditorDemo();
+        
+        JMenu menu = new JMenu("File");
+        menu.add(new AbstractAction("Make Image") {
+            public void actionPerformed(ActionEvent e) {
+                JFileChooser chooser  = new JFileChooser();
+                int option = chooser.showSaveDialog(demo);
+                if(option == JFileChooser.APPROVE_OPTION) {
+                    File file = chooser.getSelectedFile();
+                    demo.writeJPEGImage(file);
+                }
+            }});
+        menu.add(new AbstractAction("Print") {
+            public void actionPerformed(ActionEvent e) {
+                    PrinterJob printJob = PrinterJob.getPrinterJob();
+                    printJob.setPrintable(demo);
+                    if (printJob.printDialog()) {
+                        try {
+                            printJob.print();
+                        } catch (Exception ex) {
+                            ex.printStackTrace();
+                        }
+                    }
+            }});
+        JPopupMenu.setDefaultLightWeightPopupEnabled(false);
+        JMenuBar menuBar = new JMenuBar();
+        menuBar.add(menu);
+        frame.setJMenuBar(menuBar);
+        frame.getContentPane().add(demo);
+        frame.pack();
+        frame.setVisible(true);
+    }
+}
+
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/GraphFromGraphMLDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/GraphFromGraphMLDemo.java
new file mode 100644
index 0000000..8cf837e
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/GraphFromGraphMLDemo.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JMenuBar;
+import javax.swing.JPanel;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.xml.sax.SAXException;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.io.GraphMLReader;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.AbstractModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.GraphMouseListener;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.renderers.GradientVertexRenderer;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+import edu.uci.ics.jung.visualization.renderers.BasicVertexLabelRenderer.InsidePositioner;
+
+
+/**
+ * Demonstrates loading (and visualizing) a graph from a GraphML file.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+public class GraphFromGraphMLDemo {
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<Number, Number> vv;
+    
+    /**
+     * Creates an instance showing a simple graph with controls to demonstrate the zoom features.
+     * @param filename the file containing the graph data we're reading
+     * @throws ParserConfigurationException if a SAX parser cannot be constructed
+     * @throws SAXException if the SAX parser factory cannot be constructed
+     * @throws IOException if the file cannot be read
+     */
+    public GraphFromGraphMLDemo(String filename) throws ParserConfigurationException, SAXException, IOException {
+        
+    	Supplier<Number> vertexFactory = new Supplier<Number>() {
+    		int n = 0;
+    		public Number get() { return n++; }
+    	};
+    	Supplier<Number> edgeFactory = new Supplier<Number>() {
+    		int n = 0;
+    		public Number get() { return n++; }
+    	};
+    	
+    	GraphMLReader<DirectedGraph<Number,Number>, Number, Number> gmlr = 
+    	    new GraphMLReader<DirectedGraph<Number,Number>, Number, Number>(vertexFactory, edgeFactory);
+    	final DirectedGraph<Number,Number> graph = new DirectedSparseMultigraph<Number,Number>();
+    	gmlr.load(filename, graph);
+    	
+        // create a simple graph for the demo
+        vv =  new VisualizationViewer<Number,Number>(new FRLayout<Number,Number>(graph));
+
+        vv.addGraphMouseListener(new TestGraphMouseListener<Number>());
+        vv.getRenderer().setVertexRenderer(
+        		new GradientVertexRenderer<Number,Number>(
+        				Color.white, Color.red, 
+        				Color.white, Color.blue,
+        				vv.getPickedVertexState(),
+        				false));
+        
+        // add my listeners for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        vv.setEdgeToolTipTransformer(new Function<Number,String>() {
+			public String apply(Number edge) {
+				return "E"+graph.getEndpoints(edge).toString();
+			}});
+        
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        vv.getRenderer().getVertexLabelRenderer().setPositioner(new InsidePositioner());
+        vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.AUTO);
+        
+        // create a frome to hold the graph
+        final JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        content.add(panel);
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        final AbstractModalGraphMouse graphMouse = new DefaultModalGraphMouse<Number,Number>();
+        vv.setGraphMouse(graphMouse);
+        vv.addKeyListener(graphMouse.getModeKeyListener());
+
+        JMenuBar menubar = new JMenuBar();
+        menubar.add(graphMouse.getModeMenu());
+        panel.setCorner(menubar);
+
+        
+        vv.addKeyListener(graphMouse.getModeKeyListener());
+        vv.setToolTipText("<html><center>Type 'p' for Pick mode<p>Type 't' for Transform mode");
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+
+        JPanel controls = new JPanel();
+        controls.add(plus);
+        controls.add(minus);
+        content.add(controls, BorderLayout.SOUTH);
+
+        frame.pack();
+        frame.setVisible(true);
+    }
+    
+    /**
+     * A nested class to demo the GraphMouseListener finding the
+     * right vertices after zoom/pan
+     */
+    static class TestGraphMouseListener<V> implements GraphMouseListener<V> {
+        
+    		public void graphClicked(V v, MouseEvent me) {
+    		    System.err.println("Vertex "+v+" was clicked at ("+me.getX()+","+me.getY()+")");
+    		}
+    		public void graphPressed(V v, MouseEvent me) {
+    		    System.err.println("Vertex "+v+" was pressed at ("+me.getX()+","+me.getY()+")");
+    		}
+    		public void graphReleased(V v, MouseEvent me) {
+    		    System.err.println("Vertex "+v+" was released at ("+me.getX()+","+me.getY()+")");
+    		}
+    }
+
+    /**
+     * @param args if this contains at least one element, the first will be used as the file to read
+     * @throws ParserConfigurationException if a SAX parser cannot be constructed
+     * @throws SAXException if the SAX parser factory cannot be constructed
+     * @throws IOException if the file cannot be read
+     */
+    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException 
+    {
+    	String filename = "simple.graphml";
+    	if(args.length > 0) filename = args[0];
+        new GraphFromGraphMLDemo(filename);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/GraphZoomScrollPaneDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/GraphZoomScrollPaneDemo.java
new file mode 100644
index 0000000..31a59a7
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/GraphZoomScrollPaneDemo.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Paint;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.layout.KKLayout;
+import edu.uci.ics.jung.graph.DirectedSparseGraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.AbstractModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.GraphMouseListener;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.renderers.GradientVertexRenderer;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+import edu.uci.ics.jung.visualization.renderers.BasicVertexLabelRenderer.InsidePositioner;
+
+
+/**
+ * Demonstrates the use of <code>GraphZoomScrollPane</code>.
+ * This class shows the <code>VisualizationViewer</code> zooming
+ * and panning capabilities, using horizontal and
+ * vertical scrollbars.
+ *
+ * <p>This demo also shows ToolTips on graph vertices and edges,
+ * and a key listener to change graph mouse modes.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+public class GraphZoomScrollPaneDemo {
+
+    /**
+     * the graph
+     */
+    DirectedSparseGraph<String, Number> graph;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<String, Number> vv;
+    
+    /**
+     * create an instance of a simple graph with controls to
+     * demo the zoom features.
+     * 
+     */
+    public GraphZoomScrollPaneDemo() {
+        
+        // create a simple graph for the demo
+        graph = new DirectedSparseGraph<String, Number>();
+        String[] v = createVertices(10);
+        createEdges(v);
+        
+        ImageIcon sandstoneIcon = null;
+        String imageLocation = "/images/Sandstone.jpg";
+        try {
+            	sandstoneIcon = 
+            	    new ImageIcon(getClass().getResource(imageLocation));
+        } catch(Exception ex) {
+            System.err.println("Can't load \""+imageLocation+"\"");
+        }
+        final ImageIcon icon = sandstoneIcon;
+        vv =  new VisualizationViewer<String,Number>(new KKLayout<String,Number>(graph));
+        
+        if(icon != null) {
+            vv.addPreRenderPaintable(new VisualizationViewer.Paintable(){
+                public void paint(Graphics g) {
+                    Dimension d = vv.getSize();
+                    g.drawImage(icon.getImage(),0,0,d.width,d.height,vv);
+                }
+                public boolean useTransform() { return false; }
+            });
+        }
+        vv.addPostRenderPaintable(new VisualizationViewer.Paintable(){
+            int x;
+            int y;
+            Font font;
+            FontMetrics metrics;
+            int swidth;
+            int sheight;
+            String str = "GraphZoomScrollPane Demo";
+            
+            public void paint(Graphics g) {
+                Dimension d = vv.getSize();
+                if(font == null) {
+                    font = new Font(g.getFont().getName(), Font.BOLD, 30);
+                    metrics = g.getFontMetrics(font);
+                    swidth = metrics.stringWidth(str);
+                    sheight = metrics.getMaxAscent()+metrics.getMaxDescent();
+                    x = (d.width-swidth)/2;
+                    y = (int)(d.height-sheight*1.5);
+                }
+                g.setFont(font);
+                Color oldColor = g.getColor();
+                g.setColor(Color.lightGray);
+                g.drawString(str, x, y);
+                g.setColor(oldColor);
+            }
+            public boolean useTransform() {
+                return false;
+            }
+        });
+
+        vv.addGraphMouseListener(new TestGraphMouseListener<String>());
+        vv.getRenderer().setVertexRenderer(
+        		new GradientVertexRenderer<String,Number>(
+        				Color.white, Color.red, 
+        				Color.white, Color.blue,
+        				vv.getPickedVertexState(),
+        				false));
+        vv.getRenderContext().setEdgeDrawPaintTransformer(Functions.<Paint>constant(Color.lightGray));
+        vv.getRenderContext().setArrowFillPaintTransformer(Functions.<Paint>constant(Color.lightGray));
+        vv.getRenderContext().setArrowDrawPaintTransformer(Functions.<Paint>constant(Color.lightGray));
+        
+        // add my listeners for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        vv.setEdgeToolTipTransformer(new Function<Number,String>() {
+			public String apply(Number edge) {
+				return "E"+graph.getEndpoints(edge).toString();
+			}});
+        
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        vv.getRenderer().getVertexLabelRenderer().setPositioner(new InsidePositioner());
+        vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.AUTO);
+        vv.setForeground(Color.lightGray);
+        
+        // create a frome to hold the graph
+        final JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        content.add(panel);
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        final AbstractModalGraphMouse graphMouse = new DefaultModalGraphMouse<String,Number>();
+        vv.setGraphMouse(graphMouse);
+        
+        vv.addKeyListener(graphMouse.getModeKeyListener());
+        vv.setToolTipText("<html><center>Type 'p' for Pick mode<p>Type 't' for Transform mode");
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+
+        JButton reset = new JButton("reset");
+        reset.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).setToIdentity();
+				vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).setToIdentity();
+			}});
+
+        JPanel controls = new JPanel();
+        controls.add(plus);
+        controls.add(minus);
+        controls.add(reset);
+        content.add(controls, BorderLayout.SOUTH);
+
+        frame.pack();
+        frame.setVisible(true);
+    }
+    
+    /**
+     * create some vertices
+     * @param count how many to create
+     * @return the Vertices in an array
+     */
+    private String[] createVertices(int count) {
+        String[] v = new String[count];
+        for (int i = 0; i < count; i++) {
+        	v[i] = "V"+i;
+            graph.addVertex(v[i]);
+        }
+        return v;
+    }
+
+    /**
+     * create edges for this demo graph
+     * @param v an array of Vertices to connect
+     */
+    void createEdges(String[] v) {
+        graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[3], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[4], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[4], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[3], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[1], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[1], v[4], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[8], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[3], v[8], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[6], v[7], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[7], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[9], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[9], v[8], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[7], v[6], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[6], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[4], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[5], v[4], EdgeType.DIRECTED);
+    }
+
+    /**
+     * A nested class to demo the GraphMouseListener finding the
+     * right vertices after zoom/pan
+     */
+    static class TestGraphMouseListener<V> implements GraphMouseListener<V> {
+        
+    		public void graphClicked(V v, MouseEvent me) {
+    		    System.err.println("Vertex "+v+" was clicked at ("+me.getX()+","+me.getY()+")");
+    		}
+    		public void graphPressed(V v, MouseEvent me) {
+    		    System.err.println("Vertex "+v+" was pressed at ("+me.getX()+","+me.getY()+")");
+    		}
+    		public void graphReleased(V v, MouseEvent me) {
+    		    System.err.println("Vertex "+v+" was released at ("+me.getX()+","+me.getY()+")");
+    		}
+    }
+
+    public static void main(String[] args) 
+    {
+        new GraphZoomScrollPaneDemo();
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/ImageEdgeLabelDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/ImageEdgeLabelDemo.java
new file mode 100644
index 0000000..94bc963
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/ImageEdgeLabelDemo.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.net.URL;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer;
+import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer;
+
+/**
+ * Demonstrates the use of images on graph edge labels.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+public class ImageEdgeLabelDemo extends JApplet {
+
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = -4332663871914930864L;
+	
+	private static final int VERTEX_COUNT=11;
+
+	/**
+     * the graph
+     */
+    DirectedGraph<Number, Number> graph;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<Number, Number> vv;
+    
+    public ImageEdgeLabelDemo() {
+        
+        // create a simple graph for the demo
+        graph = new DirectedSparseMultigraph<Number,Number>();
+        createGraph(VERTEX_COUNT);
+        
+        FRLayout<Number, Number> layout = new FRLayout<Number, Number>(graph);
+        layout.setMaxIterations(100);
+        vv =  new VisualizationViewer<Number, Number>(layout, new Dimension(400,400));
+        
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv.getPickedEdgeState(), Color.black, Color.cyan));
+
+        vv.setBackground(Color.white);
+        
+        vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.cyan));
+        vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.cyan));
+        vv.getRenderContext().setEdgeLabelTransformer(new Function<Number,String>() {
+        	URL url = getClass().getResource("/images/lightning-s.gif");
+			public String apply(Number input) {
+				return "<html><img src="+url+" height=10 width=21>";
+			}});
+        
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        vv.setEdgeToolTipTransformer(new ToStringLabeller());
+        Container content = getContentPane();
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        content.add(panel);
+        
+        final DefaultModalGraphMouse<Number, Number> graphMouse = new DefaultModalGraphMouse<Number, Number>();
+        vv.setGraphMouse(graphMouse);
+        vv.addKeyListener(graphMouse.getModeKeyListener());
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+
+        JComboBox<Mode> modeBox = graphMouse.getModeComboBox();
+        JPanel modePanel = new JPanel();
+        modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
+        modePanel.add(modeBox);
+        
+        JPanel scaleGrid = new JPanel(new GridLayout(1,0));
+        scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        JPanel controls = new JPanel();
+        scaleGrid.add(plus);
+        scaleGrid.add(minus);
+        controls.add(scaleGrid);
+        controls.add(modePanel);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+    
+    /**
+     * create some vertices
+     * @param count how many to create
+     * @return the Vertices in an array
+     */
+    private void createGraph(int vertexCount) {
+        for (int i = 0; i < vertexCount; i++) {
+            graph.addVertex(i);
+        }
+    	int j=0;
+        graph.addEdge(j++, 0, 1, EdgeType.DIRECTED);
+        graph.addEdge(j++, 3, 0, EdgeType.DIRECTED);
+        graph.addEdge(j++, 0, 4, EdgeType.DIRECTED);
+        graph.addEdge(j++, 4, 5, EdgeType.DIRECTED);
+        graph.addEdge(j++, 5, 3, EdgeType.DIRECTED);
+        graph.addEdge(j++, 2, 1, EdgeType.DIRECTED);
+        graph.addEdge(j++, 4, 1, EdgeType.DIRECTED);
+        graph.addEdge(j++, 8, 2, EdgeType.DIRECTED);
+        graph.addEdge(j++, 3, 8, EdgeType.DIRECTED);
+        graph.addEdge(j++, 6, 7, EdgeType.DIRECTED);
+        graph.addEdge(j++, 7, 5, EdgeType.DIRECTED);
+        graph.addEdge(j++, 0, 9, EdgeType.DIRECTED);
+        graph.addEdge(j++, 9, 8, EdgeType.DIRECTED);
+        graph.addEdge(j++, 7, 6, EdgeType.DIRECTED);
+        graph.addEdge(j++, 6, 5, EdgeType.DIRECTED);
+        graph.addEdge(j++, 4, 2, EdgeType.DIRECTED);
+        graph.addEdge(j++, 5, 4, EdgeType.DIRECTED);
+        graph.addEdge(j++, 4, 10, EdgeType.DIRECTED);
+        graph.addEdge(j++, 10, 4, EdgeType.DIRECTED);
+    }
+
+    public static void main(String[] args) {
+        JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+        content.add(new ImageEdgeLabelDemo());
+        frame.pack();
+        frame.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/InternalFrameSatelliteViewDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/InternalFrameSatelliteViewDemo.java
new file mode 100644
index 0000000..dbccacd
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/InternalFrameSatelliteViewDemo.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDesktopPane;
+import javax.swing.JFrame;
+import javax.swing.JInternalFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import edu.uci.ics.jung.algorithms.layout.ISOMLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode;
+import edu.uci.ics.jung.visualization.control.SatelliteVisualizationViewer;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+
+/**
+ * Similar to the SatelliteViewDemo, but using JInternalFrame.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+public class InternalFrameSatelliteViewDemo {
+
+    static final String instructions = 
+        "<html>"+
+        "<b><h2><center>Instructions for Mouse Listeners</center></h2></b>"+
+        "<p>There are two modes, Transforming and Picking."+
+        "<p>The modes are selected with a toggle button."+
+        
+        "<p><p><b>Transforming Mode:</b>"+
+        "<ul>"+
+        "<li>Mouse1+drag pans the graph"+
+        "<li>Mouse1+Shift+drag rotates the graph"+
+        "<li>Mouse1+CTRL(or Command)+drag shears the graph"+
+        "</ul>"+
+        
+        "<b>Picking Mode:</b>"+
+        "<ul>"+
+        "<li>Mouse1 on a Vertex selects the vertex"+
+        "<li>Mouse1 elsewhere unselects all Vertices"+
+        "<li>Mouse1+Shift on a Vertex adds/removes Vertex selection"+
+        "<li>Mouse1+drag on a Vertex moves all selected Vertices"+
+        "<li>Mouse1+drag elsewhere selects Vertices in a region"+
+        "<li>Mouse1+Shift+drag adds selection of Vertices in a new region"+
+        "<li>Mouse1+CTRL on a Vertex selects the vertex and centers the display on it"+
+        "</ul>"+
+       "<b>Both Modes:</b>"+
+       "<ul>"+
+        "<li>Mousewheel scales the layout > 1 and scales the view < 1";
+    
+    /**
+     * the graph
+     */
+    Graph<String,Number> graph;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<String,Number> vv;
+    VisualizationViewer<String,Number> satellite;
+    
+    JInternalFrame dialog;
+    
+    JDesktopPane desktop;
+
+    /**
+     * create an instance of a simple graph with controls to
+     * demo the zoom features.
+     * 
+     */
+    public InternalFrameSatelliteViewDemo() {
+        
+        // create a simple graph for the demo
+        graph = TestGraphs.getOneComponentGraph();
+
+        Layout<String,Number> layout = new ISOMLayout<String,Number>(graph);
+
+        vv = new VisualizationViewer<String,Number>(layout, new Dimension(600,600));
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv.getPickedEdgeState(), Color.black, Color.cyan));
+        vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(vv.getPickedVertexState(), Color.red, Color.yellow));
+
+        // add my listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        final DefaultModalGraphMouse<String, Number> graphMouse
+        	= new DefaultModalGraphMouse<String, Number>();
+        vv.setGraphMouse(graphMouse);
+
+        satellite =
+            new SatelliteVisualizationViewer<String,Number>(vv, new Dimension(200,200));
+        satellite.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(satellite.getPickedEdgeState(), Color.black, Color.cyan));
+        satellite.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(satellite.getPickedVertexState(), Color.red, Color.yellow));
+
+        ScalingControl satelliteScaler = new CrossoverScalingControl();
+        satellite.scaleToLayout(satelliteScaler);
+        
+        JFrame frame = new JFrame();
+        desktop = new JDesktopPane();
+        Container content = frame.getContentPane();
+        JPanel panel = new JPanel(new BorderLayout());
+        panel.add(desktop);
+        content.add(panel);
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        
+        JInternalFrame vvFrame = new JInternalFrame();
+        vvFrame.getContentPane().add(vv);
+        vvFrame.pack();
+        vvFrame.setVisible(true); //necessary as of 1.3
+        desktop.add(vvFrame);
+        try {
+            vvFrame.setSelected(true);
+        } catch (java.beans.PropertyVetoException e) {}
+
+        dialog = new JInternalFrame();
+        desktop.add(dialog);
+        content = dialog.getContentPane();
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        JButton dismiss = new JButton("Dismiss");
+        dismiss.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                dialog.setVisible(false);
+            }
+        });
+        JButton help = new JButton("Help");
+        help.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                JOptionPane.showInternalMessageDialog(dialog, instructions,
+                        "Instructions", JOptionPane.PLAIN_MESSAGE);
+            }
+        });
+        JPanel controls = new JPanel(new GridLayout(2,2));
+        controls.add(plus);
+        controls.add(minus);
+        controls.add(dismiss);
+        controls.add(help);
+        content.add(satellite);
+        content.add(controls, BorderLayout.SOUTH);
+         
+        JButton zoomer = new JButton("Show Satellite View");
+        zoomer.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                dialog.pack();
+                dialog.setLocation(desktop.getWidth()-dialog.getWidth(),0);
+                dialog.show();
+                try {
+                    dialog.setSelected(true);
+                } catch (java.beans.PropertyVetoException ex) {}
+            }
+        });
+
+        JComboBox<Mode> modeBox = graphMouse.getModeComboBox();
+        modeBox.addItemListener(((ModalGraphMouse)satellite.getGraphMouse()).getModeListener());
+        JPanel p = new JPanel();
+        p.add(zoomer);
+        p.add(modeBox);
+
+        frame.getContentPane().add(p, BorderLayout.SOUTH);
+        frame.setSize(800, 800);
+        frame.setVisible(true);
+    }
+
+    public static void main(String[] args) {
+        new InternalFrameSatelliteViewDemo();
+    }
+}
+
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/L2RTreeLayoutDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/L2RTreeLayoutDemo.java
new file mode 100644
index 0000000..d32ea8b
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/L2RTreeLayoutDemo.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridLayout;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JToggleButton;
+
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.PolarPoint;
+import edu.uci.ics.jung.algorithms.layout.RadialTreeLayout;
+import edu.uci.ics.jung.algorithms.layout.TreeLayout;
+import edu.uci.ics.jung.graph.DelegateForest;
+import edu.uci.ics.jung.graph.DelegateTree;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Forest;
+import edu.uci.ics.jung.graph.Tree;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.EdgeShape;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.layout.LayoutTransition;
+import edu.uci.ics.jung.visualization.util.Animator;
+
+/**
+ * A variant of TreeLayoutDemo that rotates the view by 90 degrees from the 
+ * default orientation.
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class L2RTreeLayoutDemo extends JApplet {
+
+    /**
+     * the graph
+     */
+    Forest<String,Integer> graph;
+    
+    Supplier<DirectedGraph<String,Integer>> graphFactory = 
+    	new Supplier<DirectedGraph<String,Integer>>() {
+
+			public DirectedGraph<String, Integer> get() {
+				return new DirectedSparseMultigraph<String,Integer>();
+			}
+		};
+			
+		Supplier<Tree<String,Integer>> treeFactory =
+		new Supplier<Tree<String,Integer>> () {
+
+		public Tree<String, Integer> get() {
+			return new DelegateTree<String,Integer>(graphFactory);
+		}
+	};
+	
+	Supplier<Integer> edgeFactory = new Supplier<Integer>() {
+		int i=0;
+		public Integer get() {
+			return i++;
+		}};
+    
+    Supplier<String> vertexFactory = new Supplier<String>() {
+    	int i=0;
+		public String get() {
+			return "V"+i++;
+		}};
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<String,Integer> vv;
+    
+    VisualizationServer.Paintable rings;
+    
+    String root;
+    
+    TreeLayout<String,Integer> treeLayout;
+    
+    RadialTreeLayout<String,Integer> radialLayout;
+
+    public L2RTreeLayoutDemo() {
+        
+        // create a simple graph for the demo
+        graph = new DelegateForest<String,Integer>();
+
+        createTree();
+        
+        treeLayout = new TreeLayout<String,Integer>(graph);
+        radialLayout = new RadialTreeLayout<String,Integer>(graph);
+        radialLayout.setSize(new Dimension(600,600));
+        vv =  new VisualizationViewer<String,Integer>(treeLayout, new Dimension(600,600));
+        vv.setBackground(Color.white);
+        vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.quadCurve(graph));
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        vv.getRenderContext().setArrowFillPaintTransformer(Functions.<Paint>constant(Color.lightGray));
+        rings = new Rings();
+        
+        setLtoR(vv);
+        
+        Container content = getContentPane();
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        content.add(panel);
+        
+        final DefaultModalGraphMouse<String, Integer> graphMouse
+        	= new DefaultModalGraphMouse<String, Integer>();
+
+        vv.setGraphMouse(graphMouse);
+        
+        JComboBox<Mode> modeBox = graphMouse.getModeComboBox();
+        modeBox.addItemListener(graphMouse.getModeListener());
+        graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING);
+
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        JToggleButton radial = new JToggleButton("Radial");
+        radial.addItemListener(new ItemListener() {
+
+			public void itemStateChanged(ItemEvent e) {
+				if(e.getStateChange() == ItemEvent.SELECTED) {
+					
+					LayoutTransition<String,Integer> lt =
+						new LayoutTransition<String,Integer>(vv, treeLayout, radialLayout);
+					Animator animator = new Animator(lt);
+					animator.start();
+					vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
+					vv.addPreRenderPaintable(rings);
+				} else {
+					LayoutTransition<String,Integer> lt =
+						new LayoutTransition<String,Integer>(vv, radialLayout, treeLayout);
+					Animator animator = new Animator(lt);
+					animator.start();
+					vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
+					setLtoR(vv);
+					vv.removePreRenderPaintable(rings);
+				}
+
+				vv.repaint();
+			}});
+
+        JPanel scaleGrid = new JPanel(new GridLayout(1,0));
+        scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom"));
+
+        JPanel controls = new JPanel();
+        scaleGrid.add(plus);
+        scaleGrid.add(minus);
+        controls.add(radial);
+        controls.add(scaleGrid);
+        controls.add(modeBox);
+
+        content.add(controls, BorderLayout.SOUTH);
+    }
+    
+    private void setLtoR(VisualizationViewer<String,Integer> vv) {
+    	Layout<String,Integer> layout = vv.getModel().getGraphLayout();
+    	Dimension d = layout.getSize();
+    	Point2D center = new Point2D.Double(d.width/2, d.height/2);
+    	vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).rotate(-Math.PI/2, center);
+    }
+    
+    class Rings implements VisualizationServer.Paintable {
+    	
+    	Collection<Double> depths;
+    	
+    	public Rings() {
+    		depths = getDepths();
+    	}
+    	
+    	private Collection<Double> getDepths() {
+    		Set<Double> depths = new HashSet<Double>();
+    		Map<String,PolarPoint> polarLocations = radialLayout.getPolarLocations();
+    		for(String v : graph.getVertices()) {
+    			PolarPoint pp = polarLocations.get(v);
+    			depths.add(pp.getRadius());
+    		}
+    		return depths;
+    	}
+
+		public void paint(Graphics g) {
+			g.setColor(Color.lightGray);
+		
+			Graphics2D g2d = (Graphics2D)g;
+			Point2D center = radialLayout.getCenter();
+
+			Ellipse2D ellipse = new Ellipse2D.Double();
+			for(double d : depths) {
+				ellipse.setFrameFromDiagonal(center.getX()-d, center.getY()-d, 
+						center.getX()+d, center.getY()+d);
+				Shape shape = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).transform(ellipse);
+				g2d.draw(shape);
+			}
+		}
+
+		public boolean useTransform() {
+			return true;
+		}
+    }
+    
+    /**
+     * 
+     */
+    private void createTree() {
+    	graph.addVertex("V0");
+    	graph.addEdge(edgeFactory.get(), "V0", "V1");
+    	graph.addEdge(edgeFactory.get(), "V0", "V2");
+    	graph.addEdge(edgeFactory.get(), "V1", "V4");
+    	graph.addEdge(edgeFactory.get(), "V2", "V3");
+    	graph.addEdge(edgeFactory.get(), "V2", "V5");
+    	graph.addEdge(edgeFactory.get(), "V4", "V6");
+    	graph.addEdge(edgeFactory.get(), "V4", "V7");
+    	graph.addEdge(edgeFactory.get(), "V3", "V8");
+    	graph.addEdge(edgeFactory.get(), "V6", "V9");
+    	graph.addEdge(edgeFactory.get(), "V4", "V10");
+    	
+       	graph.addVertex("A0");
+       	graph.addEdge(edgeFactory.get(), "A0", "A1");
+       	graph.addEdge(edgeFactory.get(), "A0", "A2");
+       	graph.addEdge(edgeFactory.get(), "A0", "A3");
+       	
+       	graph.addVertex("B0");
+    	graph.addEdge(edgeFactory.get(), "B0", "B1");
+    	graph.addEdge(edgeFactory.get(), "B0", "B2");
+    	graph.addEdge(edgeFactory.get(), "B1", "B4");
+    	graph.addEdge(edgeFactory.get(), "B2", "B3");
+    	graph.addEdge(edgeFactory.get(), "B2", "B5");
+    	graph.addEdge(edgeFactory.get(), "B4", "B6");
+    	graph.addEdge(edgeFactory.get(), "B4", "B7");
+    	graph.addEdge(edgeFactory.get(), "B3", "B8");
+    	graph.addEdge(edgeFactory.get(), "B6", "B9");
+       	
+    }
+
+
+    public static void main(String[] args) {
+        JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+        content.add(new L2RTreeLayoutDemo());
+        frame.pack();
+        frame.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/LensDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/LensDemo.java
new file mode 100644
index 0000000..e331ca7
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/LensDemo.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.Icon;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenuBar;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.plaf.basic.BasicLabelUI;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.StaticLayout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseGraph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationModel;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.LensMagnificationGraphMousePlugin;
+import edu.uci.ics.jung.visualization.control.ModalLensGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.transform.HyperbolicTransformer;
+import edu.uci.ics.jung.visualization.transform.LayoutLensSupport;
+import edu.uci.ics.jung.visualization.transform.LensSupport;
+import edu.uci.ics.jung.visualization.transform.MagnifyTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.HyperbolicShapeTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.MagnifyShapeTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.ViewLensSupport;
+
+/**
+ * Demonstrates the use of <code>HyperbolicTransform</code>
+ * and <code>MagnifyTransform</code>
+ * applied to either the model (graph layout) or the view
+ * (VisualizationViewer)
+ * The hyperbolic transform is applied in an elliptical lens
+ * that affects that part of the visualization.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class LensDemo extends JApplet {
+
+    /**
+     * the graph
+     */
+    Graph<String,Number> graph;
+    
+    FRLayout<String,Number> graphLayout;
+    
+    /**
+     * a grid shaped graph
+     */
+    Graph<String,Number> grid;
+    
+    Layout<String,Number> gridLayout;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<String,Number> vv;
+
+    /**
+     * provides a Hyperbolic lens for the view
+     */
+    LensSupport hyperbolicViewSupport;
+    /**
+     * provides a magnification lens for the view
+     */
+    LensSupport magnifyViewSupport;
+    
+    /**
+     * provides a Hyperbolic lens for the model
+     */
+    LensSupport hyperbolicLayoutSupport;
+    /**
+     * provides a magnification lens for the model
+     */
+    LensSupport magnifyLayoutSupport;
+    
+    ScalingControl scaler;
+    
+    /**
+     * create an instance of a simple graph with controls to
+     * demo the zoomand hyperbolic features.
+     * 
+     */
+    public LensDemo() {
+        
+        // create a simple graph for the demo
+        graph = TestGraphs.getOneComponentGraph();
+        
+        graphLayout = new FRLayout<String,Number>(graph);
+        graphLayout.setMaxIterations(1000);
+
+        Dimension preferredSize = new Dimension(600,600);
+        Map<String,Point2D> map = new HashMap<String,Point2D>();
+        Function<String,Point2D> vlf =
+        	Functions.forMap(map);
+        grid = this.generateVertexGrid(map, preferredSize, 25);
+        gridLayout = new StaticLayout<String,Number>(grid, vlf, preferredSize);
+        
+        final VisualizationModel<String,Number> visualizationModel = 
+            new DefaultVisualizationModel<String,Number>(graphLayout, preferredSize);
+        vv =  new VisualizationViewer<String,Number>(visualizationModel, preferredSize);
+
+        PickedState<String> ps = vv.getPickedVertexState();
+        PickedState<Number> pes = vv.getPickedEdgeState();
+        vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(ps, Color.red, Color.yellow));
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(pes, Color.black, Color.cyan));
+        vv.setBackground(Color.white);
+        
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        
+        final Function<? super String,Shape> ovals = vv.getRenderContext().getVertexShapeTransformer();
+        final Function<? super String,Shape> squares = 
+        	Functions.<Shape>constant(new Rectangle2D.Float(-10,-10,20,20));
+
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        Container content = getContentPane();
+        GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv);
+        content.add(gzsp);
+        
+        /**
+         * the regular graph mouse for the normal view
+         */
+        final DefaultModalGraphMouse<String,Number> graphMouse
+        	= new DefaultModalGraphMouse<String,Number>();
+
+        vv.setGraphMouse(graphMouse);
+        vv.addKeyListener(graphMouse.getModeKeyListener());
+        
+        hyperbolicViewSupport = 
+            new ViewLensSupport<String,Number>(vv, new HyperbolicShapeTransformer(vv, 
+            		vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)), 
+                    new ModalLensGraphMouse());
+        hyperbolicLayoutSupport = 
+            new LayoutLensSupport<String,Number>(vv, new HyperbolicTransformer(vv, 
+            		vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT)),
+                    new ModalLensGraphMouse());
+        magnifyViewSupport = 
+            new ViewLensSupport<String,Number>(vv, new MagnifyShapeTransformer(vv,
+            		vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)),
+                    new ModalLensGraphMouse(new LensMagnificationGraphMousePlugin(1.f, 6.f, .2f)));
+        magnifyLayoutSupport = 
+            new LayoutLensSupport<String,Number>(vv, new MagnifyTransformer(vv, 
+            		vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT)),
+                    new ModalLensGraphMouse(new LensMagnificationGraphMousePlugin(1.f, 6.f, .2f)));
+        hyperbolicLayoutSupport.getLensTransformer().setLensShape(hyperbolicViewSupport.getLensTransformer().getLensShape());
+        magnifyViewSupport.getLensTransformer().setLensShape(hyperbolicLayoutSupport.getLensTransformer().getLensShape());
+        magnifyLayoutSupport.getLensTransformer().setLensShape(magnifyViewSupport.getLensTransformer().getLensShape());
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        ButtonGroup radio = new ButtonGroup();
+        JRadioButton normal = new JRadioButton("None");
+        normal.addItemListener(new ItemListener() {
+            public void itemStateChanged(ItemEvent e) {
+                if(e.getStateChange() == ItemEvent.SELECTED) {
+                    if(hyperbolicViewSupport != null) {
+                        hyperbolicViewSupport.deactivate();
+                    }
+                    if(hyperbolicLayoutSupport != null) {
+                        hyperbolicLayoutSupport.deactivate();
+                    }
+                    if(magnifyViewSupport != null) {
+                        magnifyViewSupport.deactivate();
+                    }
+                    if(magnifyLayoutSupport != null) {
+                        magnifyLayoutSupport.deactivate();
+                    }
+                }
+            }
+        });
+
+        final JRadioButton hyperView = new JRadioButton("Hyperbolic View");
+        hyperView.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                hyperbolicViewSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
+            }
+        });
+        final JRadioButton hyperModel = new JRadioButton("Hyperbolic Layout");
+        hyperModel.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                hyperbolicLayoutSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
+            }
+        });
+        final JRadioButton magnifyView = new JRadioButton("Magnified View");
+        magnifyView.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                magnifyViewSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
+            }
+        });
+        final JRadioButton magnifyModel = new JRadioButton("Magnified Layout");
+        magnifyModel.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                magnifyLayoutSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
+            }
+        });
+        JLabel modeLabel = new JLabel("     Mode Menu >>");
+        modeLabel.setUI(new VerticalLabelUI(false));
+        radio.add(normal);
+        radio.add(hyperModel);
+        radio.add(hyperView);
+        radio.add(magnifyModel);
+        radio.add(magnifyView);
+        normal.setSelected(true);
+        
+        graphMouse.addItemListener(hyperbolicLayoutSupport.getGraphMouse().getModeListener());
+        graphMouse.addItemListener(hyperbolicViewSupport.getGraphMouse().getModeListener());
+        graphMouse.addItemListener(magnifyLayoutSupport.getGraphMouse().getModeListener());
+        graphMouse.addItemListener(magnifyViewSupport.getGraphMouse().getModeListener());
+        
+        ButtonGroup graphRadio = new ButtonGroup();
+        JRadioButton graphButton = new JRadioButton("Graph");
+        graphButton.setSelected(true);
+        graphButton.addItemListener(new ItemListener() {
+
+            public void itemStateChanged(ItemEvent e) {
+                if(e.getStateChange() == ItemEvent.SELECTED) {
+                    visualizationModel.setGraphLayout(graphLayout);
+                    vv.getRenderContext().setVertexShapeTransformer(ovals);
+                    vv.getRenderContext().setVertexLabelTransformer(
+                    	new ToStringLabeller());
+                    vv.repaint();
+                }
+            }});
+        JRadioButton gridButton = new JRadioButton("Grid");
+        gridButton.addItemListener(new ItemListener() {
+
+            public void itemStateChanged(ItemEvent e) {
+                if(e.getStateChange() == ItemEvent.SELECTED) {
+                    visualizationModel.setGraphLayout(gridLayout);
+                    vv.getRenderContext().setVertexShapeTransformer(squares);
+                    vv.getRenderContext().setVertexLabelTransformer(Functions.<String>constant(null));
+                    vv.repaint();
+                }
+            }});
+        graphRadio.add(graphButton);
+        graphRadio.add(gridButton);
+        
+        JPanel modePanel = new JPanel(new GridLayout(3,1));
+        modePanel.setBorder(BorderFactory.createTitledBorder("Display"));
+        modePanel.add(graphButton);
+        modePanel.add(gridButton);
+
+        JMenuBar menubar = new JMenuBar();
+        menubar.add(graphMouse.getModeMenu());
+        gzsp.setCorner(menubar);
+        
+
+        Box controls = Box.createHorizontalBox();
+        JPanel zoomControls = new JPanel(new GridLayout(2,1));
+        zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        JPanel hyperControls = new JPanel(new GridLayout(3,2));
+        hyperControls.setBorder(BorderFactory.createTitledBorder("Examiner Lens"));
+        zoomControls.add(plus);
+        zoomControls.add(minus);
+        
+        hyperControls.add(normal);
+        hyperControls.add(new JLabel());
+
+        hyperControls.add(hyperModel);
+        hyperControls.add(magnifyModel);
+        
+        hyperControls.add(hyperView);
+        hyperControls.add(magnifyView);
+        
+        controls.add(zoomControls);
+        controls.add(hyperControls);
+        controls.add(modePanel);
+        controls.add(modeLabel);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+
+    private Graph<String,Number> generateVertexGrid(Map<String,Point2D> vlf,
+            Dimension d, int interval) {
+        int count = d.width/interval * d.height/interval;
+        Graph<String,Number> graph = new SparseGraph<String,Number>();
+        for(int i=0; i<count; i++) {
+            int x = interval*i;
+            int y = x / d.width * interval;
+            x %= d.width;
+            
+            Point2D location = new Point2D.Float(x, y);
+            String vertex = "v"+i;
+            vlf.put(vertex, location);
+            graph.addVertex(vertex);
+        }
+        return graph;
+    }
+    
+    static class VerticalLabelUI extends BasicLabelUI
+    {
+    	static {
+    		labelUI = new VerticalLabelUI(false);
+    	}
+    	
+    	protected boolean clockwise;
+    	VerticalLabelUI( boolean clockwise )
+    	{
+    		super();
+    		this.clockwise = clockwise;
+    	}
+    	
+
+        public Dimension getPreferredSize(JComponent c) 
+        {
+        	Dimension dim = super.getPreferredSize(c);
+        	return new Dimension( dim.height, dim.width );
+        }	
+
+        private static Rectangle paintIconR = new Rectangle();
+        private static Rectangle paintTextR = new Rectangle();
+        private static Rectangle paintViewR = new Rectangle();
+        private static Insets paintViewInsets = new Insets(0, 0, 0, 0);
+
+    	public void paint(Graphics g, JComponent c) 
+        {
+
+        	
+            JLabel label = (JLabel)c;
+            String text = label.getText();
+            Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon();
+
+            if ((icon == null) && (text == null)) {
+                return;
+            }
+
+            FontMetrics fm = g.getFontMetrics();
+            paintViewInsets = c.getInsets(paintViewInsets);
+
+            paintViewR.x = paintViewInsets.left;
+            paintViewR.y = paintViewInsets.top;
+        	
+        	// Use inverted height & width
+            paintViewR.height = c.getWidth() - (paintViewInsets.left + paintViewInsets.right);
+            paintViewR.width = c.getHeight() - (paintViewInsets.top + paintViewInsets.bottom);
+
+            paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
+            paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;
+
+            String clippedText = 
+                layoutCL(label, fm, text, icon, paintViewR, paintIconR, paintTextR);
+
+        	Graphics2D g2 = (Graphics2D) g;
+        	AffineTransform tr = g2.getTransform();
+        	if( clockwise )
+        	{
+    	    	g2.rotate( Math.PI / 2 ); 
+        		g2.translate( 0, - c.getWidth() );
+        	}
+        	else
+        	{
+    	    	g2.rotate( - Math.PI / 2 ); 
+        		g2.translate( - c.getHeight(), 0 );
+        	}
+
+        	if (icon != null) {
+                icon.paintIcon(c, g, paintIconR.x, paintIconR.y);
+            }
+
+            if (text != null) {
+                int textX = paintTextR.x;
+                int textY = paintTextR.y + fm.getAscent();
+
+                if (label.isEnabled()) {
+                    paintEnabledText(label, g, clippedText, textX, textY);
+                }
+                else {
+                    paintDisabledText(label, g, clippedText, textX, textY);
+                }
+            }
+        	
+        	
+        	g2.setTransform( tr );
+        }
+    }
+
+    public static void main(String[] args) {
+        JFrame f = new JFrame();
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.getContentPane().add(new LensDemo());
+        f.pack();
+        f.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/LensVertexImageShaperDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/LensVertexImageShaperDemo.java
new file mode 100644
index 0000000..d411791
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/LensVertexImageShaperDemo.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.GridLayout;
+import java.awt.Paint;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.graph.DirectedSparseGraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.LayeredIcon;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.decorators.VertexIconShapeTransformer;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.renderers.Checkmark;
+import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer;
+import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer;
+import edu.uci.ics.jung.visualization.transform.LayoutLensSupport;
+import edu.uci.ics.jung.visualization.transform.LensSupport;
+import edu.uci.ics.jung.visualization.transform.shape.MagnifyImageLensSupport;
+
+
+/**
+ * Demonstrates the use of images to represent graph vertices.
+ * The images are added to the DefaultGraphLabelRenderer and can
+ * either be offset from the vertex, or centered on the vertex.
+ * Additionally, the relative positioning of the label and
+ * image is controlled by subclassing the DefaultGraphLabelRenderer
+ * and setting the appropriate properties on its JLabel superclass
+ *  FancyGraphLabelRenderer
+ * 
+ * The images used in this demo (courtesy of slashdot.org) are
+ * rectangular but with a transparent background. When vertices
+ * are represented by these images, it looks better if the actual
+ * shape of the opaque part of the image is computed so that the
+ * edge arrowheads follow the visual shape of the image. This demo
+ * uses the FourPassImageShaper class to compute the Shape from
+ * an image with transparent background.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+public class LensVertexImageShaperDemo extends JApplet {
+
+	   /**
+	 * 
+	 */
+	private static final long serialVersionUID = 5432239991020505763L;
+
+	/**
+     * the graph
+     */
+    DirectedSparseGraph<Number, Number> graph;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<Number,Number> vv;
+    
+    /**
+     * some icon names to use
+     */
+    String[] iconNames = {
+            "apple",
+            "os",
+            "x",
+            "linux",
+            "inputdevices",
+            "wireless",
+            "graphics3",
+            "gamespcgames",
+            "humor",
+            "music",
+            "privacy"
+    };
+    
+    LensSupport viewSupport;
+    LensSupport modelSupport;
+    LensSupport magnifyLayoutSupport;
+    LensSupport magnifyViewSupport;
+    /**
+     * create an instance of a simple graph with controls to
+     * demo the zoom features.
+     * 
+     */
+    public LensVertexImageShaperDemo() {
+        
+        // create a simple graph for the demo
+        graph = new DirectedSparseGraph<Number,Number>();
+        Number[] vertices = createVertices(11);
+        
+        // a Map for the labels
+        Map<Number,String> map = new HashMap<Number,String>();
+        for(int i=0; i<vertices.length; i++) {
+            map.put(vertices[i], iconNames[i%iconNames.length]);
+        }
+        
+        // a Map for the Icons
+        Map<Number,Icon> iconMap = new HashMap<Number,Icon>();
+        for(int i=0; i<vertices.length; i++) {
+            String name = "/images/topic"+iconNames[i]+".gif";
+            try {
+                Icon icon = 
+                    new LayeredIcon(new ImageIcon(LensVertexImageShaperDemo.class.getResource(name)).getImage());
+                iconMap.put(vertices[i], icon);
+            } catch(Exception ex) {
+                System.err.println("You need slashdoticons.jar in your classpath to see the image "+name);
+            }
+        }
+        
+        createEdges(vertices);
+        
+        FRLayout<Number, Number> layout = new FRLayout<Number, Number>(graph);
+        layout.setMaxIterations(100);
+        vv =  new VisualizationViewer<Number, Number>(layout, new Dimension(600,600));
+        
+        Function<Number,Paint> vpf = 
+            new PickableVertexPaintTransformer<Number>(vv.getPickedVertexState(), Color.white, Color.yellow);
+        vv.getRenderContext().setVertexFillPaintTransformer(vpf);
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv.getPickedEdgeState(), Color.black, Color.cyan));
+
+        vv.setBackground(Color.white);
+        
+        final Function<Number,String> vertexStringerImpl = 
+            new VertexStringerImpl<Number>(map);
+        vv.getRenderContext().setVertexLabelTransformer(vertexStringerImpl);
+        vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.cyan));
+        vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.cyan));
+        
+        
+        // features on and off. For a real application, use VertexIconAndShapeFunction instead.
+        final VertexIconShapeTransformer<Number> vertexImageShapeFunction =
+            new VertexIconShapeTransformer<Number>(new EllipseVertexShapeTransformer<Number>());
+
+        final Function<Number, Icon> vertexIconFunction = Functions.forMap(iconMap);
+        
+        vertexImageShapeFunction.setIconMap(iconMap);
+        
+        vv.getRenderContext().setVertexShapeTransformer(vertexImageShapeFunction);
+        vv.getRenderContext().setVertexIconTransformer(vertexIconFunction);
+
+        
+        // Get the pickedState and add a listener that will decorate the
+        // Vertex images with a checkmark icon when they are picked
+        PickedState<Number> ps = vv.getPickedVertexState();
+        ps.addItemListener(new PickWithIconListener(vertexIconFunction));
+        
+        vv.addPostRenderPaintable(new VisualizationViewer.Paintable(){
+            int x;
+            int y;
+            Font font;
+            FontMetrics metrics;
+            int swidth;
+            int sheight;
+            String str = "Thank You, slashdot.org, for the images!";
+            
+            public void paint(Graphics g) {
+                Dimension d = vv.getSize();
+                if(font == null) {
+                    font = new Font(g.getFont().getName(), Font.BOLD, 20);
+                    metrics = g.getFontMetrics(font);
+                    swidth = metrics.stringWidth(str);
+                    sheight = metrics.getMaxAscent()+metrics.getMaxDescent();
+                    x = (d.width-swidth)/2;
+                    y = (int)(d.height-sheight*1.5);
+                }
+                g.setFont(font);
+                Color oldColor = g.getColor();
+                g.setColor(Color.lightGray);
+                g.drawString(str, x, y);
+                g.setColor(oldColor);
+            }
+            public boolean useTransform() {
+                return false;
+            }
+        });
+
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        Container content = getContentPane();
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        content.add(panel);
+        
+        final DefaultModalGraphMouse<Number, Number> graphMouse
+        	= new DefaultModalGraphMouse<Number, Number>();
+        vv.setGraphMouse(graphMouse);
+        
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        JComboBox<Mode> modeBox = graphMouse.getModeComboBox();
+        JPanel modePanel = new JPanel();
+        modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
+        modePanel.add(modeBox);
+        
+        JPanel scaleGrid = new JPanel(new GridLayout(1,0));
+        scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        JPanel controls = new JPanel();
+        scaleGrid.add(plus);
+        scaleGrid.add(minus);
+        controls.add(scaleGrid);
+
+        controls.add(modePanel);
+        content.add(controls, BorderLayout.SOUTH);
+        
+        this.viewSupport = new MagnifyImageLensSupport<Number,Number>(vv);
+//        	new ViewLensSupport<Number,Number>(vv, new HyperbolicShapeTransformer(vv, 
+//        		vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)), 
+//                new ModalLensGraphMouse());
+
+        this.modelSupport = new LayoutLensSupport<Number,Number>(vv);
+        
+        graphMouse.addItemListener(modelSupport.getGraphMouse().getModeListener());
+        graphMouse.addItemListener(viewSupport.getGraphMouse().getModeListener());
+
+        ButtonGroup radio = new ButtonGroup();
+        JRadioButton none = new JRadioButton("None");
+        none.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                if(viewSupport != null) {
+                    viewSupport.deactivate();
+                }
+                if(modelSupport != null) {
+                    modelSupport.deactivate();
+                }
+            }
+        });
+        none.setSelected(true);
+
+        JRadioButton hyperView = new JRadioButton("View");
+        hyperView.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                viewSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
+            }
+        });
+
+        JRadioButton hyperModel = new JRadioButton("Layout");
+        hyperModel.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                modelSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
+            }
+        });
+        radio.add(none);
+        radio.add(hyperView);
+        radio.add(hyperModel);
+        
+        JMenuBar menubar = new JMenuBar();
+        JMenu modeMenu = graphMouse.getModeMenu();
+        menubar.add(modeMenu);
+
+        JPanel lensPanel = new JPanel(new GridLayout(2,0));
+        lensPanel.setBorder(BorderFactory.createTitledBorder("Lens"));
+        lensPanel.add(none);
+        lensPanel.add(hyperView);
+        lensPanel.add(hyperModel);
+        controls.add(lensPanel);
+    }
+    
+    /**
+     * A simple implementation of VertexStringer that
+     * gets Vertex labels from a Map  
+     * 
+     * @author Tom Nelson 
+     *
+     *
+     */
+    class VertexStringerImpl<V> implements Function<V,String> {
+
+        Map<V,String> map = new HashMap<V,String>();
+        
+        boolean enabled = true;
+        
+        public VertexStringerImpl(Map<V,String> map) {
+            this.map = map;
+        }
+        
+        /**
+         * @see edu.uci.ics.jung.graph.decorators.VertexStringer#getLabel(edu.uci.ics.jung.graph.Vertex)
+         */
+        public String apply(V v) {
+            if(isEnabled()) {
+                return map.get(v);
+            } else {
+                return "";
+            }
+        }
+
+        /**
+         * @return Returns the enabled.
+         */
+        public boolean isEnabled() {
+            return enabled;
+        }
+
+        /**
+         * @param enabled The enabled to set.
+         */
+        public void setEnabled(boolean enabled) {
+            this.enabled = enabled;
+        }
+    }
+    
+    /**
+     * create some vertices
+     * @param count how many to create
+     * @return the Vertices in an array
+     */
+    private Number[] createVertices(int count) {
+        Number[] v = new Number[count];
+        for (int i = 0; i < count; i++) {
+            v[i] = new Integer(i);
+            graph.addVertex(v[i]);
+        }
+        return v;
+    }
+
+    /**
+     * create edges for this demo graph
+     * @param v an array of Vertices to connect
+     */
+    void createEdges(Number[] v) {
+        graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[3], v[0], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[4], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[4], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[5], v[3], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[2], v[1], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[4], v[1], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[8], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[3], v[8], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[6], v[7], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[7], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[9], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[9], v[8], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[7], v[6], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[6], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[4], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[5], v[4], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[4], v[10], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[10], v[4], EdgeType.DIRECTED);
+    }
+    
+    public static class PickWithIconListener implements ItemListener {
+        Function<Number, Icon> imager;
+        Icon checked;
+        
+        public PickWithIconListener(Function<Number, Icon> imager) {
+            this.imager = imager;
+            checked = new Checkmark(Color.red);
+        }
+
+        public void itemStateChanged(ItemEvent e) {
+            Icon icon = imager.apply((Number)e.getItem());
+            if(icon != null && icon instanceof LayeredIcon) {
+                if(e.getStateChange() == ItemEvent.SELECTED) {
+                    ((LayeredIcon)icon).add(checked);
+                } else {
+                    ((LayeredIcon)icon).remove(checked);
+                }
+            }
+        }
+    }
+
+    public static void main(String[] args) {
+        JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+        content.add(new LensVertexImageShaperDemo());
+        frame.pack();
+        frame.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/MinimumSpanningTreeDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/MinimumSpanningTreeDemo.java
new file mode 100644
index 0000000..cc2a9e6
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/MinimumSpanningTreeDemo.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.layout.KKLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.StaticLayout;
+import edu.uci.ics.jung.algorithms.layout.TreeLayout;
+import edu.uci.ics.jung.algorithms.shortestpath.MinimumSpanningForest2;
+import edu.uci.ics.jung.graph.DelegateForest;
+import edu.uci.ics.jung.graph.DelegateTree;
+import edu.uci.ics.jung.graph.Forest;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationModel;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.EdgeShape;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.picking.MultiPickedState;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/**
+ * Demonstrates a single graph with 3 layouts in 3 views.
+ * The first view is an undirected graph using KKLayout
+ * The second view show a TreeLayout view of a MinimumSpanningTree
+ * of the first graph. The third view shows the complete graph
+ * of the first view, using the layout positions of the 
+ * MinimumSpanningTree tree view.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class MinimumSpanningTreeDemo extends JApplet {
+
+     /**
+     * the graph
+     */
+    Graph<String,Number> graph;
+    Forest<String,Number> tree;
+
+    /**
+     * the visual components and renderers for the graph
+     */
+    VisualizationViewer<String,Number> vv0;
+    VisualizationViewer<String,Number> vv1;
+    VisualizationViewer<String,Number> vv2;
+    
+    /**
+     * the normal Function
+     */
+    MutableTransformer layoutTransformer;
+    
+    Dimension preferredSize = new Dimension(300,300);
+    Dimension preferredLayoutSize = new Dimension(400,400);
+    Dimension preferredSizeRect = new Dimension(500,250);
+    
+    /**
+     * create an instance of a simple graph in two views with controls to
+     * demo the zoom features.
+     * 
+     */
+    public MinimumSpanningTreeDemo() {
+        
+        // create a simple graph for the demo
+        // both models will share one graph
+        graph = 
+        	TestGraphs.getDemoGraph();
+        
+        MinimumSpanningForest2<String,Number> prim = 
+        	new MinimumSpanningForest2<String,Number>(graph,
+        		new DelegateForest<String,Number>(), DelegateTree.<String,Number>getFactory(),
+        		Functions.<Double>constant(1.0));
+        
+        tree = prim.getForest();
+        
+        // create two layouts for the one graph, one layout for each model
+        Layout<String,Number> layout0 = new KKLayout<String,Number>(graph);
+        layout0.setSize(preferredLayoutSize);
+        Layout<String,Number> layout1 = new TreeLayout<String,Number>(tree);
+        Layout<String,Number> layout2 = new StaticLayout<String,Number>(graph, layout1);
+
+        // create the two models, each with a different layout
+        VisualizationModel<String,Number> vm0 =
+            new DefaultVisualizationModel<String,Number>(layout0, preferredSize);
+        VisualizationModel<String,Number> vm1 =
+            new DefaultVisualizationModel<String,Number>(layout1, preferredSizeRect);
+        VisualizationModel<String,Number> vm2 = 
+            new DefaultVisualizationModel<String,Number>(layout2, preferredSizeRect);
+        	
+        // create the two views, one for each model
+        // they share the same renderer
+        vv0 = new VisualizationViewer<String,Number>(vm0, preferredSize);
+        vv1 = new VisualizationViewer<String,Number>(vm1, preferredSizeRect);
+        vv2 = new VisualizationViewer<String,Number>(vm2, preferredSizeRect);
+        
+        vv1.getRenderContext().setMultiLayerTransformer(vv0.getRenderContext().getMultiLayerTransformer());
+        vv2.getRenderContext().setMultiLayerTransformer(vv0.getRenderContext().getMultiLayerTransformer());
+
+        vv1.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph));
+        
+        vv0.addChangeListener(vv1);
+        vv1.addChangeListener(vv2);
+        
+        vv0.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        vv2.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        
+        Color back = Color.decode("0xffffbb");
+        vv0.setBackground(back);
+        vv1.setBackground(back);
+        vv2.setBackground(back);
+        
+        vv0.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR);
+        vv0.setForeground(Color.darkGray);
+        vv1.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR);
+        vv1.setForeground(Color.darkGray);
+        vv2.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR);
+        vv2.setForeground(Color.darkGray);
+        
+        // share one PickedState between the two views
+        PickedState<String> ps = new MultiPickedState<String>();
+        vv0.setPickedVertexState(ps);
+        vv1.setPickedVertexState(ps);
+        vv2.setPickedVertexState(ps);
+
+        PickedState<Number> pes = new MultiPickedState<Number>();
+        vv0.setPickedEdgeState(pes);
+        vv1.setPickedEdgeState(pes);
+        vv2.setPickedEdgeState(pes);
+
+        
+        // set an edge paint function that will show picking for edges
+        vv0.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv0.getPickedEdgeState(), Color.black, Color.red));
+        vv0.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(vv0.getPickedVertexState(),
+                Color.red, Color.yellow));
+        vv1.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv1.getPickedEdgeState(), Color.black, Color.red));
+        vv1.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(vv1.getPickedVertexState(),
+                Color.red, Color.yellow));
+        
+        // add default listeners for ToolTips
+        vv0.setVertexToolTipTransformer(new ToStringLabeller());
+        vv1.setVertexToolTipTransformer(new ToStringLabeller());
+        vv2.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        vv0.setLayout(new BorderLayout());
+        vv1.setLayout(new BorderLayout());
+        vv2.setLayout(new BorderLayout());
+        
+        Font font = vv0.getFont().deriveFont(Font.BOLD, 16);
+        JLabel vv0Label = new JLabel("<html>Original Graph<p>using KKLayout");
+        vv0Label.setFont(font);
+        JLabel vv1Label = new JLabel("Minimum Spanning Trees");
+        vv1Label.setFont(font);
+        JLabel vv2Label = new JLabel("Original Graph using TreeLayout");
+        vv2Label.setFont(font);
+        JPanel flow0 = new JPanel();
+        flow0.setOpaque(false);
+        JPanel flow1 = new JPanel();
+        flow1.setOpaque(false);
+        JPanel flow2 = new JPanel();
+        flow2.setOpaque(false);
+        flow0.add(vv0Label);
+        flow1.add(vv1Label);
+        flow2.add(vv2Label);
+        vv0.add(flow0, BorderLayout.NORTH);
+        vv1.add(flow1, BorderLayout.NORTH);
+        vv2.add(flow2, BorderLayout.NORTH);
+        
+        Container content = getContentPane();
+        JPanel grid = new JPanel(new GridLayout(0,1));
+        JPanel panel = new JPanel(new BorderLayout());
+        panel.add(new GraphZoomScrollPane(vv0), BorderLayout.WEST);
+        grid.add(new GraphZoomScrollPane(vv1));
+        grid.add(new GraphZoomScrollPane(vv2));
+        panel.add(grid);
+
+        content.add(panel);
+        
+        // create a GraphMouse for each view
+        DefaultModalGraphMouse<String, Number> gm0 = new DefaultModalGraphMouse<String, Number>();
+        DefaultModalGraphMouse<String, Number> gm1 = new DefaultModalGraphMouse<String, Number>();
+        DefaultModalGraphMouse<String, Number> gm2 = new DefaultModalGraphMouse<String, Number>();
+
+        vv0.setGraphMouse(gm0);
+        vv1.setGraphMouse(gm1);
+        vv2.setGraphMouse(gm2);
+
+        // create zoom buttons for scaling the Function that is
+        // shared between the two models.
+        final ScalingControl scaler = new CrossoverScalingControl();
+        
+        vv0.scaleToLayout(scaler);
+        vv1.scaleToLayout(scaler);
+        vv2.scaleToLayout(scaler);
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv1, 1.1f, vv1.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv1, 1/1.1f, vv1.getCenter());
+            }
+        });
+        
+        JPanel zoomPanel = new JPanel(new GridLayout(1,2));
+        zoomPanel.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        
+        JPanel modePanel = new JPanel();
+        modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
+        gm1.getModeComboBox().addItemListener(gm2.getModeListener());
+        gm1.getModeComboBox().addItemListener(gm0.getModeListener());
+        modePanel.add(gm1.getModeComboBox());
+
+        JPanel controls = new JPanel();
+        zoomPanel.add(plus);
+        zoomPanel.add(minus);
+        controls.add(zoomPanel);
+        controls.add(modePanel);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+
+    public static void main(String[] args) {
+        JFrame f = new JFrame();
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.getContentPane().add(new MinimumSpanningTreeDemo());
+        f.pack();
+        f.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/MultiViewDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/MultiViewDemo.java
new file mode 100644
index 0000000..95d2883
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/MultiViewDemo.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.GridLayout;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.geom.Rectangle2D;
+
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationModel;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.AnimatedPickingGraphMousePlugin;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.LayoutScalingControl;
+import edu.uci.ics.jung.visualization.control.PickingGraphMousePlugin;
+import edu.uci.ics.jung.visualization.control.RotatingGraphMousePlugin;
+import edu.uci.ics.jung.visualization.control.ScalingGraphMousePlugin;
+import edu.uci.ics.jung.visualization.control.ShearingGraphMousePlugin;
+import edu.uci.ics.jung.visualization.control.TranslatingGraphMousePlugin;
+import edu.uci.ics.jung.visualization.control.ViewScalingControl;
+import edu.uci.ics.jung.visualization.decorators.EdgeShape;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.picking.MultiPickedState;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.picking.ShapePickSupport;
+
+/**
+ * Demonstrates 3 views of one graph in one model with one layout.
+ * Each view uses a different scaling graph mouse.
+ * 
+ * @author Tom Nelson 
+ * 
+ */
+ at SuppressWarnings("serial")
+public class MultiViewDemo extends JApplet {
+
+    /**
+     * the graph
+     */
+    Graph<String,Number> graph;
+
+    /**
+     * the visual components and renderers for the graph
+     */
+    VisualizationViewer<String,Number> vv1;
+    VisualizationViewer<String,Number> vv2;
+    VisualizationViewer<String,Number> vv3;
+    
+    /**
+     * the normal Function
+     */
+//    MutableTransformer Function;
+    
+    Dimension preferredSize = new Dimension(300,300);
+    
+    final String messageOne = "The mouse wheel will scale the model's layout when activated"+
+    " in View 1. Since all three views share the same layout Function, all three views will"+
+    " show the same scaling of the layout.";
+    
+    final String messageTwo = "The mouse wheel will scale the view when activated in"+
+    " View 2. Since all three views share the same view Function, all three views will be affected.";
+    
+    final String messageThree = "   The mouse wheel uses a 'crossover' feature in View 3."+
+    " When the combined layout and view scale is greater than '1', the model's layout will be scaled."+
+    " Since all three views share the same layout Function, all three views will show the same "+
+    " scaling of the layout.\n   When the combined scale is less than '1', the scaling function"+
+    " crosses over to the view, and then, since all three views share the same view Function,"+
+    " all three views will show the same scaling.";
+    
+    JTextArea textArea;
+    JScrollPane scrollPane;
+    
+    /**
+     * create an instance of a simple graph in two views with controls to
+     * demo the zoom features.
+     * 
+     */
+    public MultiViewDemo() {
+        
+        // create a simple graph for the demo
+        graph = TestGraphs.getOneComponentGraph();
+        
+        // create one layout for the graph
+        FRLayout<String,Number> layout = new FRLayout<String,Number>(graph);
+        layout.setMaxIterations(1000);
+        
+        // create one model that all 3 views will share
+        VisualizationModel<String,Number> visualizationModel =
+            new DefaultVisualizationModel<String,Number>(layout, preferredSize);
+ 
+        // create 3 views that share the same model
+        vv1 = new VisualizationViewer<String,Number>(visualizationModel, preferredSize);
+        vv2 = new VisualizationViewer<String,Number>(visualizationModel, preferredSize);
+        vv3 = new VisualizationViewer<String,Number>(visualizationModel, preferredSize);
+        
+        vv1.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph));
+        vv2.getRenderContext().setVertexShapeTransformer(
+        		Functions.<Shape>constant(new Rectangle2D.Float(-6,-6,12,12)));
+
+        vv2.getRenderContext().setEdgeShapeTransformer(EdgeShape.quadCurve(graph));
+        
+        vv3.getRenderContext().setEdgeShapeTransformer(EdgeShape.cubicCurve(graph));
+
+//        Function = vv1.getLayoutTransformer();
+//        vv2.setLayoutTransformer(Function);
+//        vv3.setLayoutTransformer(Function);
+//        
+//        vv2.setViewTransformer(vv1.getViewTransformer());
+//        vv3.setViewTransformer(vv1.getViewTransformer());
+        
+        vv2.getRenderContext().setMultiLayerTransformer(vv1.getRenderContext().getMultiLayerTransformer());
+        vv3.getRenderContext().setMultiLayerTransformer(vv1.getRenderContext().getMultiLayerTransformer());
+
+        vv1.getRenderContext().getMultiLayerTransformer().addChangeListener(vv1);
+        vv2.getRenderContext().getMultiLayerTransformer().addChangeListener(vv2);
+        vv3.getRenderContext().getMultiLayerTransformer().addChangeListener(vv3);
+
+        
+        vv1.setBackground(Color.white);
+        vv2.setBackground(Color.white);
+        vv3.setBackground(Color.white);
+        
+        // create one pick support for all 3 views to share
+        GraphElementAccessor<String,Number> pickSupport = new ShapePickSupport<String,Number>(vv1);
+        vv1.setPickSupport(pickSupport);
+        vv2.setPickSupport(pickSupport);
+        vv3.setPickSupport(pickSupport);
+
+        // create one picked state for all 3 views to share
+        PickedState<Number> pes = new MultiPickedState<Number>();
+        PickedState<String> pvs = new MultiPickedState<String>();
+        vv1.setPickedVertexState(pvs);
+        vv2.setPickedVertexState(pvs);
+        vv3.setPickedVertexState(pvs);
+        vv1.setPickedEdgeState(pes);
+        vv2.setPickedEdgeState(pes);
+        vv3.setPickedEdgeState(pes);
+        
+        // set an edge paint function that shows picked edges
+        vv1.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(pes, Color.black, Color.red));
+        vv2.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(pes, Color.black, Color.red));
+        vv3.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(pes, Color.black, Color.red));
+        vv1.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(pvs, Color.red, Color.yellow));
+        vv2.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(pvs, Color.blue, Color.cyan));
+        vv3.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(pvs, Color.red, Color.yellow));
+        
+        // add default listener for ToolTips
+        vv1.setVertexToolTipTransformer(new ToStringLabeller());
+        vv2.setVertexToolTipTransformer(new ToStringLabeller());
+        vv3.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        Container content = getContentPane();
+        JPanel panel = new JPanel(new GridLayout(1,0));
+        
+        final JPanel p1 = new JPanel(new BorderLayout());
+        final JPanel p2 = new JPanel(new BorderLayout());
+        final JPanel p3 = new JPanel(new BorderLayout());
+        
+        p1.add(new GraphZoomScrollPane(vv1));
+        p2.add(new GraphZoomScrollPane(vv2));
+        p3.add(new GraphZoomScrollPane(vv3));
+        
+        JButton h1 = new JButton("?");
+        h1.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                textArea.setText(messageOne);
+                JOptionPane.showMessageDialog(p1, scrollPane, 
+                        "View 1", JOptionPane.PLAIN_MESSAGE);
+            }});
+        JButton h2 = new JButton("?");
+        h2.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                textArea.setText(messageTwo);
+                JOptionPane.showMessageDialog(p2, scrollPane, 
+                        "View 2", JOptionPane.PLAIN_MESSAGE);
+            }});
+        JButton h3 = new JButton("?");
+        h3.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                textArea.setText(messageThree);
+                textArea.setCaretPosition(0);
+                JOptionPane.showMessageDialog(p3, scrollPane, 
+                        "View 3", JOptionPane.PLAIN_MESSAGE);
+           }});
+        
+        // create a GraphMouse for each view
+        // each one has a different scaling plugin
+        DefaultModalGraphMouse<String,Number> gm1 = new DefaultModalGraphMouse<String,Number>() {
+            protected void loadPlugins() {
+                pickingPlugin = new PickingGraphMousePlugin<String,Number>();
+                animatedPickingPlugin = new AnimatedPickingGraphMousePlugin<String,Number>();
+                translatingPlugin = new TranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK);
+                scalingPlugin = new ScalingGraphMousePlugin(new LayoutScalingControl(), 0);
+                rotatingPlugin = new RotatingGraphMousePlugin();
+                shearingPlugin = new ShearingGraphMousePlugin();
+
+                add(scalingPlugin);
+                setMode(Mode.TRANSFORMING);
+            }
+        };
+
+        DefaultModalGraphMouse<String,Number> gm2 = new DefaultModalGraphMouse<String,Number>() {
+            protected void loadPlugins() {
+                pickingPlugin = new PickingGraphMousePlugin<String,Number>();
+                animatedPickingPlugin = new AnimatedPickingGraphMousePlugin<String,Number>();
+                translatingPlugin = new TranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK);
+                scalingPlugin = new ScalingGraphMousePlugin(new ViewScalingControl(), 0);
+                rotatingPlugin = new RotatingGraphMousePlugin();
+                shearingPlugin = new ShearingGraphMousePlugin();
+
+                add(scalingPlugin);
+                setMode(Mode.TRANSFORMING);
+            }
+       	
+        };
+
+        DefaultModalGraphMouse<String,Number> gm3 = new DefaultModalGraphMouse<String,Number>() {};
+        
+        vv1.setGraphMouse(gm1);
+        vv2.setGraphMouse(gm2);
+        vv3.setGraphMouse(gm3);
+
+        vv1.setToolTipText("<html><center>MouseWheel Scales Layout</center></html>");
+        vv2.setToolTipText("<html><center>MouseWheel Scales View</center></html>");
+        vv3.setToolTipText("<html><center>MouseWheel Scales Layout and<p>crosses over to view<p>ctrl+MouseWheel scales view</center></html>");
+ 
+        vv1.addPostRenderPaintable(new BannerLabel(vv1, "View 1"));
+        vv2.addPostRenderPaintable(new BannerLabel(vv2, "View 2"));
+        vv3.addPostRenderPaintable(new BannerLabel(vv3, "View 3"));
+        
+        textArea = new JTextArea(6,30);
+        scrollPane = new JScrollPane(textArea, 
+                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+        textArea.setLineWrap(true);
+        textArea.setWrapStyleWord(true);
+        textArea.setEditable(false);
+        
+        JPanel flow = new JPanel();
+        flow.add(h1);
+        flow.add(gm1.getModeComboBox());
+        p1.add(flow, BorderLayout.SOUTH);
+        flow = new JPanel();
+        flow.add(h2);
+        flow.add(gm2.getModeComboBox());
+        p2.add(flow, BorderLayout.SOUTH);
+        flow = new JPanel();
+        flow.add(h3);
+        flow.add(gm3.getModeComboBox());
+        p3.add(flow, BorderLayout.SOUTH);
+        
+        panel.add(p1);
+        panel.add(p2);
+        panel.add(p3);
+        content.add(panel);
+        
+
+    }
+    
+    class BannerLabel implements VisualizationViewer.Paintable {
+        int x;
+        int y;
+        Font font;
+        FontMetrics metrics;
+        int swidth;
+        int sheight;
+        String str;
+        VisualizationViewer<String,Number> vv;
+        
+        public BannerLabel(VisualizationViewer<String,Number> vv, String label) {
+            this.vv = vv;
+            this.str = label;
+        }
+        
+        public void paint(Graphics g) {
+            Dimension d = vv.getSize();
+            if(font == null) {
+                font = new Font(g.getFont().getName(), Font.BOLD, 30);
+                metrics = g.getFontMetrics(font);
+                swidth = metrics.stringWidth(str);
+                sheight = metrics.getMaxAscent()+metrics.getMaxDescent();
+                x = (3*d.width/2-swidth)/2;
+                y = d.height-sheight;
+            }
+            g.setFont(font);
+            Color oldColor = g.getColor();
+            g.setColor(Color.gray);
+            g.drawString(str, x, y);
+            g.setColor(oldColor);
+        }
+        public boolean useTransform() {
+            return false;
+        }
+    }
+
+    public static void main(String[] args) {
+        JFrame f = new JFrame();
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.getContentPane().add(new MultiViewDemo());
+        f.pack();
+        f.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/PersistentLayoutDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/PersistentLayoutDemo.java
new file mode 100644
index 0000000..579257a
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/PersistentLayoutDemo.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.layout.PersistentLayout;
+import edu.uci.ics.jung.visualization.layout.PersistentLayoutImpl;
+
+
+/**
+ * Demonstrates the use of <code>PersistentLayout</code>
+ * and <code>PersistentLayoutImpl</code>.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+public class PersistentLayoutDemo {
+
+    /**
+     * the graph
+     */
+	Graph<String, Number> graph = TestGraphs.getOneComponentGraph();
+
+    /**
+     * the name of the file where the layout is saved
+     */
+    String fileName;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<String,Number> vv;
+    
+    PersistentLayout<String,Number> persistentLayout;
+
+    /**
+     * create an instance of a simple graph with controls to
+     * demo the persistence and zoom features.
+     * 
+     * @param fileName where to save/restore the graph positions
+     */
+    public PersistentLayoutDemo(final String fileName) {
+        this.fileName = fileName;
+        
+        // create a simple graph for the demo
+        persistentLayout = 
+            new PersistentLayoutImpl<String,Number>(new FRLayout<String,Number>(graph));
+
+        vv = new VisualizationViewer<String,Number>(persistentLayout);
+        
+        // add my listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        DefaultModalGraphMouse<String,Number> gm = new DefaultModalGraphMouse<String,Number>();
+        vv.setGraphMouse(gm);
+        final ScalingControl scaler = new CrossoverScalingControl();
+        
+        vv.scaleToLayout(scaler);
+
+        // create a frome to hold the graph
+        final JFrame frame = new JFrame();
+        frame.getContentPane().add(new GraphZoomScrollPane(vv));
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+        // create a control panel and buttons for demo
+        // functions
+        JPanel p = new JPanel();
+        
+        JButton persist = new JButton("Save Layout");
+        // saves the graph vertex positions to a file
+        persist.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                try {
+                    persistentLayout.persist(fileName);
+                } catch (IOException e1) {
+                    System.err.println("got "+e1);
+            	}
+            }
+        });
+        p.add(persist);
+
+        JButton restore = new JButton("Restore Layout");
+        // restores the graph vertex positions from a file
+        // if new vertices were added since the last 'persist',
+        // they will be placed at random locations
+        restore.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+//                PersistentLayout<String,Number> pl = (PersistentLayout<String,Number>) vv.getGraphLayout();
+                try {
+                    persistentLayout.restore(fileName);
+                } catch (Exception e1) {
+                    e1.printStackTrace();
+                }
+            }
+        });
+        p.add(restore);
+        p.add(gm.getModeComboBox());
+
+        frame.getContentPane().add(p, BorderLayout.SOUTH);
+        frame.pack();//setSize(600, 600);
+        frame.setVisible(true);
+    }
+
+    /**
+     * a driver for this demo
+     * @param args should hold the filename for the persistence demo
+     */
+    public static void main(String[] args) {
+        String filename;
+        if (args.length >= 1)
+            filename = args[0];
+        else
+            filename = "PersistentLayoutDemo.out";
+        new PersistentLayoutDemo(filename);
+    }
+}
+
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/PluggableRendererDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/PluggableRendererDemo.java
new file mode 100644
index 0000000..6224eef
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/PluggableRendererDemo.java
@@ -0,0 +1,1198 @@
+/*
+ * Copyright (c) 2004, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ *
+ * Created on Nov 7, 2004
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GradientPaint;
+import java.awt.GridLayout;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.geom.Point2D;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.ButtonGroup;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButton;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.generators.random.MixedRandomGraphGenerator;
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.scoring.VoltageScorer;
+import edu.uci.ics.jung.algorithms.scoring.util.VertexScoreTransformer;
+import edu.uci.ics.jung.algorithms.util.SelfLoopEdgePredicate;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.AbstractPopupGraphMousePlugin;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.AbstractVertexShapeTransformer;
+import edu.uci.ics.jung.visualization.decorators.EdgeShape;
+import edu.uci.ics.jung.visualization.decorators.GradientEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.NumberFormattingTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.picking.PickedInfo;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.renderers.BasicEdgeArrowRenderingSupport;
+import edu.uci.ics.jung.visualization.renderers.CachingEdgeRenderer;
+import edu.uci.ics.jung.visualization.renderers.CachingVertexRenderer;
+import edu.uci.ics.jung.visualization.renderers.CenterEdgeArrowRenderingSupport;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;
+
+
+/**
+ * Shows off some of the capabilities of <code>PluggableRenderer</code>.
+ * This code provides examples of different ways to provide and
+ * change the various functions that provide property information
+ * to the renderer.
+ * 
+ * <p>This demo creates a random mixed-mode graph with random edge
+ * weights using <code>TestGraph.generateMixedRandomGraph</code>.
+ * It then runs <code>VoltageRanker</code> on this graph, using half
+ * of the "seed" vertices from the random graph generation as 
+ * voltage sources, and half of them as voltage sinks.
+ * 
+ * <p>What the controls do:
+ * <ul>
+ * <li>Mouse controls:
+ * <ul>
+ * <li>If your mouse has a scroll wheel, scrolling forward zooms out and 
+ * scrolling backward zooms in.
+ * <li>Left-clicking on a vertex or edge selects it, and unselects all others.
+ * <li>Middle-clicking on a vertex or edge toggles its selection state.
+ * <li>Right-clicking on a vertex brings up a pop-up menu that allows you to
+ * increase or decrease that vertex's transparency.
+ * <li>Left-clicking on the background allows you to drag the image around.
+ * <li>Hovering over a vertex tells you what its voltage is; hovering over an
+ * edge shows its identity; hovering over the background shows an informational 
+ * message.
+ * </ul>
+ * <li>Vertex stuff:
+ * <ul>
+ * <li>"vertex seed coloring": if checked, the seed vertices are colored blue, 
+ * and all other vertices are colored red.  Otherwise, all vertices are colored
+ * a slightly transparent red (except the currently "picked" vertex, which is
+ * colored transparent purple).
+ * <li>"vertex selection stroke highlighting": if checked, the picked vertex
+ * and its neighbors are all drawn with heavy borders.  Otherwise, all vertices
+ * are drawn with light borders.
+ * <li>"show vertex ranks (voltages)": if checked, each vertex is labeled with its
+ * calculated 'voltage'.  Otherwise, vertices are unlabeled.
+ * <li>"vertex degree shapes": if checked, vertices are drawn with a polygon with
+ * number of sides proportional to its degree.  Otherwise, vertices are drawn
+ * as ellipses.
+ * <li>"vertex voltage size": if checked, vertices are drawn with a size 
+ * proportional to their voltage ranking.  Otherwise, all vertices are drawn 
+ * at the same size.
+ * <li>"vertex degree ratio stretch": if checked, vertices are drawn with an
+ * aspect ratio (height/width ratio) proportional to the ratio of their indegree to
+ * their outdegree.  Otherwise, vertices are drawn with an aspect ratio of 1.
+ * <li>"filter vertices of degree < 4": if checked, does not display any vertices
+ * (or their incident edges) whose degree in the original graph is less than 4; 
+ * otherwise, all vertices are drawn.
+ * </ul>
+ * <li>Edge stuff:
+ * <ul>
+ * <li>"edge shape": selects between lines, wedges, quadratic curves, and cubic curves
+ * for drawing edges.  
+ * <li>"fill edge shapes": if checked, fills the edge shapes.  This will have no effect
+ * if "line" is selected.
+ * <li>"edge paint": selects between solid colored edges, and gradient-painted edges.
+ * Gradient painted edges are darkest in the middle for undirected edges, and darkest
+ * at the destination for directed edges.
+ * <li>"show edges": only edges of the checked types are drawn.
+ * <li>"show arrows": only arrows whose edges are of the checked types are drawn.
+ * <li>"edge weight highlighting": if checked, edges with weight greater than
+ * a threshold value are drawn using thick solid lines, and other edges are drawn
+ * using thin gray dotted lines.  (This combines edge stroke and paint.) Otherwise,
+ * all edges are drawn with thin solid lines.
+ * <li>"show edge weights": if checked, edges are labeled with their weights.
+ * Otherwise, edges are not labeled.
+ * </ul>
+ * <li>Miscellaneous (center panel)
+ * <ul>
+ * <li>"bold text": if checked, all vertex and edge labels are drawn using a
+ * boldface font.  Otherwise, a normal-weight font is used.  (Has no effect if
+ * no labels are currently visible.)
+ * <li>zoom controls: 
+ * <ul>
+ * <li>"+" zooms in, "-" zooms out
+ * <li>"zoom at mouse (wheel only)": if checked, zooming (using the mouse 
+ * scroll wheel) is centered on the location of the mouse pointer; otherwise,
+ * it is centered on the center of the visualization pane.
+ * </ul>
+ * </ul>
+ * </ul>
+ * 
+ * 
+ * @author Danyel Fisher, Joshua O'Madadhain, Tom Nelson
+ */
+ at SuppressWarnings("serial")
+public class PluggableRendererDemo extends JApplet implements ActionListener 
+{
+    protected JCheckBox v_color;
+    protected JCheckBox e_color;
+    protected JCheckBox v_stroke;
+    protected JCheckBox e_uarrow_pred;
+    protected JCheckBox e_darrow_pred;
+    protected JCheckBox e_arrow_centered;
+    protected JCheckBox v_shape;
+    protected JCheckBox v_size;
+    protected JCheckBox v_aspect;
+    protected JCheckBox v_labels;
+    protected JRadioButton e_line;
+    protected JRadioButton e_bent;
+    protected JRadioButton e_wedge;
+    protected JRadioButton e_quad;
+    protected JRadioButton e_ortho;
+    protected JRadioButton e_cubic;
+    protected JCheckBox e_labels;
+    protected JCheckBox font;
+    protected JCheckBox e_show_d;
+    protected JCheckBox e_show_u;
+    protected JCheckBox v_small;
+    protected JCheckBox zoom_at_mouse;
+    protected JCheckBox fill_edges;
+    
+	protected JRadioButton no_gradient;
+	protected JRadioButton gradient_relative;
+
+	protected static final int GRADIENT_NONE = 0;
+	protected static final int GRADIENT_RELATIVE = 1;
+	protected static int gradient_level = GRADIENT_NONE;
+
+    protected SeedFillColor<Integer> seedFillColor;
+    protected SeedDrawColor<Integer> seedDrawColor;
+    protected EdgeWeightStrokeFunction<Number> ewcs;
+    protected VertexStrokeHighlight<Integer,Number> vsh;
+    protected Function<? super Integer,String> vs;
+    protected Function<? super Integer,String> vs_none;
+    protected Function<? super Number,String> es;
+    protected Function<? super Number,String> es_none;
+    protected VertexFontTransformer<Integer> vff;
+    protected EdgeFontTransformer<Number> eff;
+    protected VertexShapeSizeAspect<Integer,Number> vssa;
+    protected DirectionDisplayPredicate<Integer,Number> show_edge;
+    protected DirectionDisplayPredicate<Integer,Number> show_arrow;
+    protected VertexDisplayPredicate<Integer,Number> show_vertex;
+    protected Predicate<Context<Graph<Integer,Number>,Number>> self_loop;
+    protected GradientPickedEdgePaintFunction<Integer,Number> edgeDrawPaint;
+    protected GradientPickedEdgePaintFunction<Integer,Number> edgeFillPaint;
+    protected final static Object VOLTAGE_KEY = "voltages";
+    protected final static Object TRANSPARENCY = "transparency";
+    
+    protected Map<Number,Number> edge_weight = new HashMap<Number,Number>();
+    protected Function<Integer, Double> voltages;
+    protected Map<Integer,Number> transparency = new HashMap<Integer,Number>();
+    
+    protected VisualizationViewer<Integer,Number> vv;
+    protected DefaultModalGraphMouse<Integer, Number> gm;
+    protected Set<Integer> seedVertices = new HashSet<Integer>();
+    
+    private Graph<Integer, Number> graph;
+    
+    public void start()
+    {
+        getContentPane().add( startFunction() );
+    }
+    
+    public static void main(String[] s ) 
+    {
+        JFrame jf = new JFrame();
+        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        JPanel jp = new PluggableRendererDemo().startFunction();
+        jf.getContentPane().add(jp);
+        jf.pack();
+        jf.setVisible(true);
+    }
+    
+    
+    public JPanel startFunction() {
+        this.graph = buildGraph();
+        
+        Layout<Integer,Number> layout = new FRLayout<Integer,Number>(graph);
+        vv = new VisualizationViewer<Integer,Number>(layout);
+        
+        vv.getRenderer().setVertexRenderer(new CachingVertexRenderer<Integer,Number>(vv));
+        vv.getRenderer().setEdgeRenderer(new CachingEdgeRenderer<Integer,Number>(vv));
+
+        PickedState<Integer> picked_state = vv.getPickedVertexState();
+
+        self_loop = new SelfLoopEdgePredicate<Integer,Number>();
+        // create decorators
+        seedFillColor = new SeedFillColor<Integer>(picked_state);
+        seedDrawColor = new SeedDrawColor<Integer>();
+        ewcs = 
+            new EdgeWeightStrokeFunction<Number>(edge_weight);
+        vsh = new VertexStrokeHighlight<Integer,Number>(graph, picked_state);
+        vff = new VertexFontTransformer<Integer>();
+        eff = new EdgeFontTransformer<Number>();
+        vs_none = Functions.constant(null);
+        es_none = Functions.constant(null);
+        vssa = new VertexShapeSizeAspect<Integer,Number>(graph, voltages);
+        show_edge = new DirectionDisplayPredicate<Integer,Number>(true, true);
+        show_arrow = new DirectionDisplayPredicate<Integer,Number>(true, false);
+        show_vertex = new VertexDisplayPredicate<Integer,Number>(false);
+
+        // uses a gradient edge if unpicked, otherwise uses picked selection
+        edgeDrawPaint = 
+            new GradientPickedEdgePaintFunction<Integer,Number>(
+                    new PickableEdgePaintTransformer<Number>(
+                            vv.getPickedEdgeState(),Color.black,Color.cyan), vv);
+        edgeFillPaint = 
+            new GradientPickedEdgePaintFunction<Integer,Number>(
+                    new PickableEdgePaintTransformer<Number>(
+                            vv.getPickedEdgeState(),Color.black,Color.cyan), vv);
+        
+        vv.getRenderContext().setVertexFillPaintTransformer(seedFillColor);
+        vv.getRenderContext().setVertexDrawPaintTransformer(seedDrawColor);
+        vv.getRenderContext().setVertexStrokeTransformer(vsh);
+        vv.getRenderContext().setVertexLabelTransformer(vs_none);
+        vv.getRenderContext().setVertexFontTransformer(vff);
+        vv.getRenderContext().setVertexShapeTransformer(vssa);
+        vv.getRenderContext().setVertexIncludePredicate(show_vertex);
+        
+        vv.getRenderContext().setEdgeDrawPaintTransformer( edgeDrawPaint );
+        vv.getRenderContext().setEdgeLabelTransformer(es_none);
+        vv.getRenderContext().setEdgeFontTransformer(eff);
+        vv.getRenderContext().setEdgeStrokeTransformer(ewcs);
+        vv.getRenderContext().setEdgeIncludePredicate(show_edge);
+        vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph));
+        vv.getRenderContext().setEdgeArrowPredicate(show_arrow);
+        
+        vv.getRenderContext().setArrowFillPaintTransformer(Functions.<Paint>constant(Color.lightGray));
+        vv.getRenderContext().setArrowDrawPaintTransformer(Functions.<Paint>constant(Color.black));
+        JPanel jp = new JPanel();
+        jp.setLayout(new BorderLayout());
+        
+        vv.setBackground(Color.white);
+        GraphZoomScrollPane scrollPane = new GraphZoomScrollPane(vv);
+        jp.add(scrollPane);
+        gm = new DefaultModalGraphMouse<Integer, Number>();
+        vv.setGraphMouse(gm);
+        gm.add(new PopupGraphMousePlugin());
+
+        addBottomControls( jp );
+        vssa.setScaling(true);
+
+        vv.setVertexToolTipTransformer(new VoltageTips<Number>());
+        vv.setToolTipText("<html><center>Use the mouse wheel to zoom<p>Click and Drag the mouse to pan<p>Shift-click and Drag to Rotate</center></html>");
+        
+
+        
+        return jp;
+    }
+    
+    /**
+     * Generates a mixed-mode random graph, runs VoltageRanker on it, and
+     * returns the resultant graph.
+     * @return the generated graph
+     */
+    public Graph<Integer,Number> buildGraph() {
+    	Supplier<Graph<Integer,Number>> graphFactory =
+    		new Supplier<Graph<Integer,Number>>() {
+    		public Graph<Integer,Number> get() {
+    			return new SparseMultigraph<Integer,Number>();
+    		}
+    	};
+    	Supplier<Integer> vertexFactory = 
+    		new Supplier<Integer>() {
+    			int count;
+				public Integer get() {
+					return count++;
+				}};
+				Supplier<Number> edgeFactory = 
+		    new Supplier<Number>() {
+			    int count;
+				public Number get() {
+					return count++;
+				}};
+        Graph<Integer,Number> g = 
+        	MixedRandomGraphGenerator.<Integer,Number>generateMixedRandomGraph(graphFactory, vertexFactory, edgeFactory,
+        		edge_weight, 20, seedVertices);
+        es = new NumberFormattingTransformer<Number>(Functions.forMap(edge_weight));
+        
+        // collect the seeds used to define the random graph
+
+        if (seedVertices.size() < 2)
+            System.out.println("need at least 2 seeds (one source, one sink)");
+        
+        // use these seeds as source and sink vertices, run VoltageRanker
+        boolean source = true;
+        Set<Integer> sources = new HashSet<Integer>();
+        Set<Integer> sinks = new HashSet<Integer>();
+        for(Integer v : seedVertices)
+        {
+            if (source)
+                sources.add(v);
+            else
+                sinks.add(v);
+            source = !source;
+        }
+        VoltageScorer<Integer, Number> voltage_scores = 
+            new VoltageScorer<Integer, Number>(g, 
+                    Functions.forMap(edge_weight), sources, sinks);
+        voltage_scores.evaluate();
+        voltages = new VertexScoreTransformer<Integer, Double>(voltage_scores);
+        vs = new NumberFormattingTransformer<Integer>(voltages);
+        
+        Collection<Integer> verts = g.getVertices();
+        
+        // assign a transparency value of 0.9 to all vertices
+        for(Integer v : verts) {
+            transparency.put(v, new Double(0.9));
+        }
+
+        // add a couple of self-loops (sanity check on rendering)
+        Integer v = verts.iterator().next(); 
+        Number e = new Float(Math.random());
+        edge_weight.put(e, e);
+        g.addEdge(e, v, v);
+        e = new Float(Math.random());
+        edge_weight.put(e, e);
+        g.addEdge(e, v, v);
+        return g;  
+    }
+    
+    /**
+     * @param jp    panel to which controls will be added
+     */
+	protected void addBottomControls(final JPanel jp) 
+    {
+        final JPanel control_panel = new JPanel();
+        jp.add(control_panel, BorderLayout.EAST);
+        control_panel.setLayout(new BorderLayout());
+        final Box vertex_panel = Box.createVerticalBox();
+        vertex_panel.setBorder(BorderFactory.createTitledBorder("Vertices"));
+        final Box edge_panel = Box.createVerticalBox();
+        edge_panel.setBorder(BorderFactory.createTitledBorder("Edges"));
+        final Box both_panel = Box.createVerticalBox();
+
+        control_panel.add(vertex_panel, BorderLayout.NORTH);
+        control_panel.add(edge_panel, BorderLayout.SOUTH);
+        control_panel.add(both_panel, BorderLayout.CENTER);
+        
+        // set up vertex controls
+        v_color = new JCheckBox("seed highlight");
+        v_color.addActionListener(this);
+        v_stroke = new JCheckBox("stroke highlight on selection");
+        v_stroke.addActionListener(this);
+        v_labels = new JCheckBox("show voltage values");
+        v_labels.addActionListener(this);
+        v_shape = new JCheckBox("shape by degree");
+        v_shape.addActionListener(this);
+        v_size = new JCheckBox("size by voltage");
+        v_size.addActionListener(this);
+        v_size.setSelected(true);
+        v_aspect = new JCheckBox("stretch by degree ratio");
+        v_aspect.addActionListener(this);
+        v_small = new JCheckBox("filter when degree < " + VertexDisplayPredicate.MIN_DEGREE);
+        v_small.addActionListener(this);
+
+        vertex_panel.add(v_color);
+        vertex_panel.add(v_stroke);
+        vertex_panel.add(v_labels);
+        vertex_panel.add(v_shape);
+        vertex_panel.add(v_size);
+        vertex_panel.add(v_aspect);
+        vertex_panel.add(v_small);
+        
+        // set up edge controls
+		JPanel gradient_panel = new JPanel(new GridLayout(1, 0));
+        gradient_panel.setBorder(BorderFactory.createTitledBorder("Edge paint"));
+		no_gradient = new JRadioButton("Solid color");
+		no_gradient.addActionListener(this);
+		no_gradient.setSelected(true);
+//		gradient_absolute = new JRadioButton("Absolute gradient");
+//		gradient_absolute.addActionListener(this);
+		gradient_relative = new JRadioButton("Gradient");
+		gradient_relative.addActionListener(this);
+		ButtonGroup bg_grad = new ButtonGroup();
+		bg_grad.add(no_gradient);
+		bg_grad.add(gradient_relative);
+		//bg_grad.add(gradient_absolute);
+		gradient_panel.add(no_gradient);
+		//gradientGrid.add(gradient_absolute);
+		gradient_panel.add(gradient_relative);
+        
+        JPanel shape_panel = new JPanel(new GridLayout(3,2));
+        shape_panel.setBorder(BorderFactory.createTitledBorder("Edge shape"));
+        e_line = new JRadioButton("line");
+        e_line.addActionListener(this);
+        e_line.setSelected(true);
+//        e_bent = new JRadioButton("bent line");
+//        e_bent.addActionListener(this);
+        e_wedge = new JRadioButton("wedge");
+        e_wedge.addActionListener(this);
+        e_quad = new JRadioButton("quad curve");
+        e_quad.addActionListener(this);
+        e_cubic = new JRadioButton("cubic curve");
+        e_cubic.addActionListener(this);
+        e_ortho = new JRadioButton("orthogonal");
+        e_ortho.addActionListener(this);
+        ButtonGroup bg_shape = new ButtonGroup();
+        bg_shape.add(e_line);
+//        bg.add(e_bent);
+        bg_shape.add(e_wedge);
+        bg_shape.add(e_quad);
+        bg_shape.add(e_ortho);
+        bg_shape.add(e_cubic);
+        shape_panel.add(e_line);
+//        shape_panel.add(e_bent);
+        shape_panel.add(e_wedge);
+        shape_panel.add(e_quad);
+        shape_panel.add(e_cubic);
+        shape_panel.add(e_ortho);
+        fill_edges = new JCheckBox("fill edge shapes");
+        fill_edges.setSelected(false);
+        fill_edges.addActionListener(this);
+        shape_panel.add(fill_edges);
+        shape_panel.setOpaque(true);
+        e_color = new JCheckBox("highlight edge weights");
+        e_color.addActionListener(this);
+        e_labels = new JCheckBox("show edge weight values");
+        e_labels.addActionListener(this);
+        e_uarrow_pred = new JCheckBox("undirected");
+        e_uarrow_pred.addActionListener(this);
+        e_darrow_pred = new JCheckBox("directed");
+        e_darrow_pred.addActionListener(this);
+        e_darrow_pred.setSelected(true);
+        e_arrow_centered = new JCheckBox("centered");
+        e_arrow_centered.addActionListener(this);
+        JPanel arrow_panel = new JPanel(new GridLayout(1,0));
+        arrow_panel.setBorder(BorderFactory.createTitledBorder("Show arrows"));
+        arrow_panel.add(e_uarrow_pred);
+        arrow_panel.add(e_darrow_pred);
+        arrow_panel.add(e_arrow_centered);
+        
+        e_show_d = new JCheckBox("directed");
+        e_show_d.addActionListener(this);
+        e_show_d.setSelected(true);
+        e_show_u = new JCheckBox("undirected");
+        e_show_u.addActionListener(this);
+        e_show_u.setSelected(true);
+        JPanel show_edge_panel = new JPanel(new GridLayout(1,0));
+        show_edge_panel.setBorder(BorderFactory.createTitledBorder("Show edges"));
+        show_edge_panel.add(e_show_u);
+        show_edge_panel.add(e_show_d);
+        
+        shape_panel.setAlignmentX(Component.LEFT_ALIGNMENT);
+        edge_panel.add(shape_panel);
+        gradient_panel.setAlignmentX(Component.LEFT_ALIGNMENT);
+        edge_panel.add(gradient_panel);
+        show_edge_panel.setAlignmentX(Component.LEFT_ALIGNMENT);
+        edge_panel.add(show_edge_panel);
+        arrow_panel.setAlignmentX(Component.LEFT_ALIGNMENT);
+        edge_panel.add(arrow_panel);
+        
+        e_color.setAlignmentX(Component.LEFT_ALIGNMENT);
+        edge_panel.add(e_color);
+        e_labels.setAlignmentX(Component.LEFT_ALIGNMENT);
+        edge_panel.add(e_labels);
+
+        // set up zoom controls
+        zoom_at_mouse = new JCheckBox("<html><center>zoom at mouse<p>(wheel only)</center></html>");
+        zoom_at_mouse.addActionListener(this);
+        zoom_at_mouse.setSelected(true);
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+
+        JPanel zoomPanel = new JPanel();
+        zoomPanel.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        plus.setAlignmentX(Component.CENTER_ALIGNMENT);
+        zoomPanel.add(plus);
+        minus.setAlignmentX(Component.CENTER_ALIGNMENT);
+        zoomPanel.add(minus);
+        zoom_at_mouse.setAlignmentX(Component.CENTER_ALIGNMENT);
+        zoomPanel.add(zoom_at_mouse);
+        
+        JPanel fontPanel = new JPanel();
+        // add font and zoom controls to center panel
+        font = new JCheckBox("bold text");
+        font.addActionListener(this);
+        font.setAlignmentX(Component.CENTER_ALIGNMENT);
+        fontPanel.add(font);
+        
+        both_panel.add(zoomPanel);
+        both_panel.add(fontPanel);
+        
+        JComboBox<?> modeBox = gm.getModeComboBox();
+        modeBox.setAlignmentX(Component.CENTER_ALIGNMENT);
+        JPanel modePanel = new JPanel(new BorderLayout()) {
+            public Dimension getMaximumSize() {
+                return getPreferredSize();
+            }
+        };
+        modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
+        modePanel.add(modeBox);
+        JPanel comboGrid = new JPanel(new GridLayout(0,1));
+        comboGrid.add(modePanel);
+        fontPanel.add(comboGrid);
+        
+        
+        JComboBox<Position> cb = new JComboBox<Position>();
+        cb.addItem(Renderer.VertexLabel.Position.N);
+        cb.addItem(Renderer.VertexLabel.Position.NE);
+        cb.addItem(Renderer.VertexLabel.Position.E);
+        cb.addItem(Renderer.VertexLabel.Position.SE);
+        cb.addItem(Renderer.VertexLabel.Position.S);
+        cb.addItem(Renderer.VertexLabel.Position.SW);
+        cb.addItem(Renderer.VertexLabel.Position.W);
+        cb.addItem(Renderer.VertexLabel.Position.NW);
+        cb.addItem(Renderer.VertexLabel.Position.N);
+        cb.addItem(Renderer.VertexLabel.Position.CNTR);
+        cb.addItem(Renderer.VertexLabel.Position.AUTO);
+        cb.addItemListener(new ItemListener() {
+			public void itemStateChanged(ItemEvent e) {
+				Renderer.VertexLabel.Position position = 
+					(Renderer.VertexLabel.Position)e.getItem();
+				vv.getRenderer().getVertexLabelRenderer().setPosition(position);
+				vv.repaint();
+			}});
+        cb.setSelectedItem(Renderer.VertexLabel.Position.SE);
+        JPanel positionPanel = new JPanel();
+        positionPanel.setBorder(BorderFactory.createTitledBorder("Label Position"));
+        positionPanel.add(cb);
+
+        comboGrid.add(positionPanel);
+
+    }
+    
+    public void actionPerformed(ActionEvent e)
+    {
+        AbstractButton source = (AbstractButton)e.getSource();
+        if (source == v_color)
+        {
+            seedFillColor.setSeedColoring(source.isSelected());
+        }
+        else if (source == e_color)
+        {
+            ewcs.setWeighted(source.isSelected());
+        }
+        else if (source == v_stroke) 
+        {
+            vsh.setHighlight(source.isSelected());
+        }
+        else if (source == v_labels)
+        {
+            if (source.isSelected())
+                vv.getRenderContext().setVertexLabelTransformer(vs);
+            else
+                vv.getRenderContext().setVertexLabelTransformer(vs_none);
+        }
+        else if (source == e_labels)
+        {
+            if (source.isSelected())
+                vv.getRenderContext().setEdgeLabelTransformer(es);
+            else
+                vv.getRenderContext().setEdgeLabelTransformer(es_none);
+        }
+        else if (source == e_uarrow_pred)
+        {
+            show_arrow.showUndirected(source.isSelected());
+        }
+        else if (source == e_darrow_pred)
+        {
+            show_arrow.showDirected(source.isSelected());
+        }
+        else if (source == e_arrow_centered)
+        {
+        	if(source.isSelected()) 
+        	{
+        		vv.getRenderer().getEdgeRenderer().setEdgeArrowRenderingSupport(
+        			new CenterEdgeArrowRenderingSupport<Integer, Number>());
+        	} 
+        	else
+        	{
+        		vv.getRenderer().getEdgeRenderer().setEdgeArrowRenderingSupport(
+        			new BasicEdgeArrowRenderingSupport<Integer, Number>());
+        	}
+        }
+        else if (source == font)
+        {
+            vff.setBold(source.isSelected());
+            eff.setBold(source.isSelected());
+        }
+        else if (source == v_shape)
+        {
+            vssa.useFunnyShapes(source.isSelected());
+        }
+        else if (source == v_size)
+        {
+            vssa.setScaling(source.isSelected());
+        }
+        else if (source == v_aspect)
+        {
+            vssa.setStretching(source.isSelected());
+        }
+        else if (source == e_line) 
+        {
+            if(source.isSelected())
+            {
+                vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph));
+            }
+        }
+        else if (source == e_ortho)
+        {
+            if (source.isSelected())
+                vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.orthogonal(graph));
+        }
+        else if (source == e_wedge)
+        {
+            if (source.isSelected())
+                vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.wedge(graph, 10));
+        }
+//        else if (source == e_bent) 
+//        {
+//            if(source.isSelected())
+//            {
+//                vv.getRenderContext().setEdgeShapeFunction(new EdgeShape.BentLine());
+//            }
+//        }
+        else if (source == e_quad) 
+        {
+            if(source.isSelected())
+            {
+                vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.quadCurve(graph));
+            }
+        }
+        else if (source == e_cubic) 
+        {
+            if(source.isSelected())
+            {
+                vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.cubicCurve(graph));
+            }
+        }
+       else if (source == e_show_d)
+        {
+            show_edge.showDirected(source.isSelected());
+        }
+        else if (source == e_show_u)
+        {
+            show_edge.showUndirected(source.isSelected());
+        }
+        else if (source == v_small)
+        {
+            show_vertex.filterSmall(source.isSelected());
+        }
+        else if(source == zoom_at_mouse)
+        {
+            gm.setZoomAtMouse(source.isSelected());
+        } 
+        else if (source == no_gradient) {
+			if (source.isSelected()) {
+				gradient_level = GRADIENT_NONE;
+			}
+		} 
+        else if (source == gradient_relative) {
+			if (source.isSelected()) {
+				gradient_level = GRADIENT_RELATIVE;
+			}
+		}
+        else if (source == fill_edges)
+        {
+        	if(source.isSelected()) {
+        		vv.getRenderContext().setEdgeFillPaintTransformer( edgeFillPaint );
+        	} else {
+        		vv.getRenderContext().setEdgeFillPaintTransformer( Functions.<Paint>constant(null) );
+        	}
+        }
+        vv.repaint();
+    }
+    
+    private final class SeedDrawColor<V> implements Function<V,Paint>
+    {
+        public Paint apply(V v)
+        {
+            return Color.BLACK;
+        }
+    }
+    
+    private final class SeedFillColor<V> implements Function<V,Paint>
+    {
+        protected PickedInfo<V> pi;
+        protected final static float dark_value = 0.8f;
+        protected final static float light_value = 0.2f;
+        protected boolean seed_coloring;
+        
+        public SeedFillColor(PickedInfo<V> pi)
+        {
+            this.pi = pi;
+            seed_coloring = false;
+        }
+
+        public void setSeedColoring(boolean b)
+        {
+            this.seed_coloring = b;
+        }
+        
+        public Paint apply(V v)
+        {
+            float alpha = transparency.get(v).floatValue();
+            if (pi.isPicked(v))
+            {
+                return new Color(1f, 1f, 0, alpha); 
+            }
+            else
+            {
+                if (seed_coloring && seedVertices.contains(v))
+                {
+                    Color dark = new Color(0, 0, dark_value, alpha);
+                    Color light = new Color(0, 0, light_value, alpha);
+                    return new GradientPaint( 0, 0, dark, 10, 0, light, true);
+                }
+                else
+                    return new Color(1f, 0, 0, alpha);
+            }
+                
+        }
+    }
+
+    private final static class EdgeWeightStrokeFunction<E>
+    implements Function<E,Stroke>
+    {
+        protected static final Stroke basic = new BasicStroke(1);
+        protected static final Stroke heavy = new BasicStroke(2);
+        protected static final Stroke dotted = RenderContext.DOTTED;
+        
+        protected boolean weighted = false;
+        protected Map<E,Number> edge_weight;
+        
+        public EdgeWeightStrokeFunction(Map<E,Number> edge_weight)
+        {
+            this.edge_weight = edge_weight;
+        }
+        
+        public void setWeighted(boolean weighted)
+        {
+            this.weighted = weighted;
+        }
+        
+        public Stroke apply(E e)
+        {
+            if (weighted)
+            {
+                if (drawHeavy(e))
+                    return heavy;
+                else
+                    return dotted;
+            }
+            else
+                return basic;
+        }
+        
+        protected boolean drawHeavy(E e)
+        {
+            double value = edge_weight.get(e).doubleValue();
+            if (value > 0.7)
+                return true;
+            else
+                return false;
+        }
+        
+    }
+    
+    private final static class VertexStrokeHighlight<V,E> implements
+    Function<V,Stroke>
+    {
+        protected boolean highlight = false;
+        protected Stroke heavy = new BasicStroke(5);
+        protected Stroke medium = new BasicStroke(3);
+        protected Stroke light = new BasicStroke(1);
+        protected PickedInfo<V> pi;
+        protected Graph<V,E> graph;
+        
+        public VertexStrokeHighlight(Graph<V,E> graph, PickedInfo<V> pi)
+        {
+        	this.graph = graph;
+            this.pi = pi;
+        }
+        
+        public void setHighlight(boolean highlight)
+        {
+            this.highlight = highlight;
+        }
+        
+        public Stroke apply(V v)
+        {
+            if (highlight)
+            {
+                if (pi.isPicked(v))
+                    return heavy;
+                else
+                {
+                	for(V w : graph.getNeighbors(v)) {
+//                    for (Iterator iter = graph.getNeighbors(v)v.getNeighbors().iterator(); iter.hasNext(); )
+//                    {
+//                        Vertex w = (Vertex)iter.next();
+                        if (pi.isPicked(w))
+                            return medium;
+                    }
+                    return light;
+                }
+            }
+            else
+                return light; 
+        }
+
+    }
+    
+    private final static class VertexFontTransformer<V> 
+    	implements Function<V,Font>
+    {
+        protected boolean bold = false;
+        Font f = new Font("Helvetica", Font.PLAIN, 12);
+        Font b = new Font("Helvetica", Font.BOLD, 12);
+        
+        public void setBold(boolean bold)
+        {
+            this.bold = bold;
+        }
+        
+        public Font apply(V v)
+        {
+            if (bold)
+                return b;
+            else
+                return f;
+        }
+    }
+
+    private final static class EdgeFontTransformer<E> 
+        implements Function<E,Font>
+{
+    protected boolean bold = false;
+    Font f = new Font("Helvetica", Font.PLAIN, 12);
+    Font b = new Font("Helvetica", Font.BOLD, 12);
+    
+    public void setBold(boolean bold)
+    {
+        this.bold = bold;
+    }
+    
+    public Font apply(E e)
+    {
+        if (bold)
+            return b;
+        else 
+            return f;
+    }
+}
+    private final static class DirectionDisplayPredicate<V,E> 
+    	implements Predicate<Context<Graph<V,E>,E>>
+    	//extends AbstractGraphPredicate<V,E>
+    {
+        protected boolean show_d;
+        protected boolean show_u;
+        
+        public DirectionDisplayPredicate(boolean show_d, boolean show_u)
+        {
+            this.show_d = show_d;
+            this.show_u = show_u;
+        }
+        
+        public void showDirected(boolean b)
+        {
+            show_d = b;
+        }
+        
+        public void showUndirected(boolean b)
+        {
+            show_u = b;
+        }
+        
+        public boolean apply(Context<Graph<V,E>,E> context)
+        {
+        	Graph<V,E> graph = context.graph;
+        	E e = context.element;
+            if (graph.getEdgeType(e) == EdgeType.DIRECTED && show_d) {
+                return true;
+            }
+            if (graph.getEdgeType(e) == EdgeType.UNDIRECTED && show_u) {
+                return true;
+            }
+            return false;
+        }
+    }
+    
+    private final static class VertexDisplayPredicate<V,E>
+    	implements Predicate<Context<Graph<V,E>,V>>
+//    	extends  AbstractGraphPredicate<V,E>
+    {
+        protected boolean filter_small;
+        protected final static int MIN_DEGREE = 4;
+        
+        public VertexDisplayPredicate(boolean filter)
+        {
+            this.filter_small = filter;
+        }
+        
+        public void filterSmall(boolean b)
+        {
+            filter_small = b;
+        }
+        
+        public boolean apply(Context<Graph<V,E>,V> context) {
+        	Graph<V,E> graph = context.graph;
+        	V v = context.element;
+//            Vertex v = (Vertex)arg0;
+            if (filter_small)
+                return (graph.degree(v) >= MIN_DEGREE);
+            else
+                return true;
+        }
+    }
+    
+    /**
+     * Controls the shape, size, and aspect ratio for each vertex.
+     * 
+     * @author Joshua O'Madadhain
+     */
+    private final static class VertexShapeSizeAspect<V,E>
+    extends AbstractVertexShapeTransformer <V>
+    implements Function<V,Shape>  {
+    	
+        protected boolean stretch = false;
+        protected boolean scale = false;
+        protected boolean funny_shapes = false;
+        protected Function<V,Double> voltages;
+        protected Graph<V,E> graph;
+//        protected AffineTransform scaleTransform = new AffineTransform();
+        
+        public VertexShapeSizeAspect(Graph<V,E> graphIn, Function<V,Double> voltagesIn)
+        {
+        	this.graph = graphIn;
+            this.voltages = voltagesIn;
+            setSizeTransformer(new Function<V,Integer>() {
+
+				public Integer apply(V v) {
+		            if (scale)
+		                return (int)(voltages.apply(v) * 30) + 20;
+		            else
+		                return 20;
+
+				}});
+            setAspectRatioTransformer(new Function<V,Float>() {
+
+				public Float apply(V v) {
+		            if (stretch) {
+		                return (float)(graph.inDegree(v) + 1) / 
+		                	(graph.outDegree(v) + 1);
+		            } else {
+		                return 1.0f;
+		            }
+				}});
+        }
+        
+		public void setStretching(boolean stretch)
+        {
+            this.stretch = stretch;
+        }
+        
+        public void setScaling(boolean scale)
+        {
+            this.scale = scale;
+        }
+        
+        public void useFunnyShapes(boolean use)
+        {
+            this.funny_shapes = use;
+        }
+        
+        public Shape apply(V v)
+        {
+            if (funny_shapes)
+            {
+                if (graph.degree(v) < 5)
+                {	
+                    int sides = Math.max(graph.degree(v), 3);
+                    return factory.getRegularPolygon(v, sides);
+                }
+                else
+                    return factory.getRegularStar(v, graph.degree(v));
+            }
+            else
+                return factory.getEllipse(v);
+        }
+    }
+    
+    /**
+     * a GraphMousePlugin that offers popup
+     * menu support
+     */
+    protected class PopupGraphMousePlugin extends AbstractPopupGraphMousePlugin
+    	implements MouseListener {
+        
+        public PopupGraphMousePlugin() {
+            this(MouseEvent.BUTTON3_MASK);
+        }
+        public PopupGraphMousePlugin(int modifiers) {
+            super(modifiers);
+        }
+        
+        /**
+         * If this event is over a Vertex, pop up a menu to
+         * allow the user to increase/decrease the voltage
+         * attribute of this Vertex
+         * @param e the event to be handled
+         */
+        @SuppressWarnings("unchecked")
+        protected void handlePopup(MouseEvent e) {
+            final VisualizationViewer<Integer,Number> vv = 
+                (VisualizationViewer<Integer,Number>)e.getSource();
+            Point2D p = e.getPoint();//vv.getRenderContext().getBasicTransformer().inverseViewTransform(e.getPoint());
+            
+            GraphElementAccessor<Integer,Number> pickSupport = vv.getPickSupport();
+            if(pickSupport != null) {
+                final Integer v = pickSupport.getVertex(vv.getGraphLayout(), p.getX(), p.getY());
+                if(v != null) {
+                    JPopupMenu popup = new JPopupMenu();
+                    popup.add(new AbstractAction("Decrease Transparency") {
+                        public void actionPerformed(ActionEvent e) {
+                        	Double value = Math.min(1, 
+                        		transparency.get(v).doubleValue()+0.1);
+                        	transparency.put(v, value);
+//                        	transparency.put(v, )transparency.get(v);
+//                            MutableDouble value = (MutableDouble)transparency.getNumber(v);
+//                            value.setDoubleValue(Math.min(1, value.doubleValue() + 0.1));
+                            vv.repaint();
+                        }
+                    });
+                    popup.add(new AbstractAction("Increase Transparency"){
+                        public void actionPerformed(ActionEvent e) {
+                        	Double value = Math.max(0, 
+                            		transparency.get(v).doubleValue()-0.1);
+                            	transparency.put(v, value);
+//                            MutableDouble value = (MutableDouble)transparency.getNumber(v);
+//                            value.setDoubleValue(Math.max(0, value.doubleValue() - 0.1));
+                            vv.repaint();
+                        }
+                    });
+                    popup.show(vv, e.getX(), e.getY());
+                } else {
+                    final Number edge = pickSupport.getEdge(vv.getGraphLayout(), p.getX(), p.getY());
+                    if(edge != null) {
+                        JPopupMenu popup = new JPopupMenu();
+                        popup.add(new AbstractAction(edge.toString()) {
+                            public void actionPerformed(ActionEvent e) {
+                                System.err.println("got "+edge);
+                            }
+                        });
+                        popup.show(vv, e.getX(), e.getY());
+                       
+                    }
+                }
+            }
+        }
+    }
+    
+    public class VoltageTips<E>
+    	implements Function<Integer,String> {
+        
+        public String apply(Integer vertex) {
+           return "Voltage:"+voltages.apply(vertex);
+        }
+    }
+    
+    public class GradientPickedEdgePaintFunction<V,E> extends GradientEdgePaintTransformer<V,E> 
+    {
+        private Function<E,Paint> defaultFunc;
+        protected boolean fill_edge = false;
+        Predicate<Context<Graph<V,E>,E>> selfLoop = new SelfLoopEdgePredicate<V,E>();
+        
+        public GradientPickedEdgePaintFunction(Function<E,Paint> defaultEdgePaintFunction, 
+                VisualizationViewer<V,E> vv) 
+        {
+            super(Color.WHITE, Color.BLACK, vv);
+            this.defaultFunc = defaultEdgePaintFunction;
+        }
+        
+        public void useFill(boolean b)
+        {
+            fill_edge = b;
+        }
+        
+        public Paint apply(E e) {
+            if (gradient_level == GRADIENT_NONE) {
+                return defaultFunc.apply(e);
+            } else {
+            	return super.apply(e);
+            }
+        }
+        
+        protected Color getColor2(E e)
+        {
+            return vv.getPickedEdgeState().isPicked(e)? Color.CYAN : c2;
+        }
+        
+//        public Paint getFillPaint(E e)
+//        {
+//            if (selfLoop.evaluateEdge(vv.getGraphLayout().getGraph(), e) || !fill_edge)
+//                return null;
+//            else
+//                return getDrawPaint(e);
+//        }
+        
+    }
+    
+}
+
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/RadialTreeLensDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/RadialTreeLensDemo.java
new file mode 100644
index 0000000..c727bf7
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/RadialTreeLensDemo.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridLayout;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JMenuBar;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.layout.PolarPoint;
+import edu.uci.ics.jung.algorithms.layout.RadialTreeLayout;
+import edu.uci.ics.jung.algorithms.layout.TreeLayout;
+import edu.uci.ics.jung.graph.DelegateForest;
+import edu.uci.ics.jung.graph.DelegateTree;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseGraph;
+import edu.uci.ics.jung.graph.Forest;
+import edu.uci.ics.jung.graph.Tree;
+import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationModel;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalLensGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.EdgeShape;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.transform.LensSupport;
+import edu.uci.ics.jung.visualization.transform.shape.HyperbolicShapeTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.ViewLensSupport;
+
+/**
+ * Shows a RadialTreeLayout view of a Forest.
+ * A hyperbolic projection lens may also be applied
+ * to the view
+ * 
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class RadialTreeLensDemo extends JApplet {
+	
+	Forest<String,Integer> graph;
+
+	Supplier<DirectedGraph<String,Integer>> graphFactory = 
+		new Supplier<DirectedGraph<String,Integer>>() {
+
+		public DirectedGraph<String, Integer> get() {
+			return new DirectedSparseGraph<String,Integer>();
+		}
+	};
+
+	Supplier<Tree<String,Integer>> treeFactory =
+		new Supplier<Tree<String,Integer>> () {
+
+		public Tree<String, Integer> get() {
+			return new DelegateTree<String,Integer>(graphFactory);
+		}
+	};
+	Supplier<Integer> edgeFactory = new Supplier<Integer>() {
+		int i=0;
+		public Integer get() {
+			return i++;
+		}
+	};
+
+	Supplier<String> vertexFactory = new Supplier<String>() {
+		int i=0;
+		public String get() {
+			return "V"+i++;
+		}
+	};
+
+	VisualizationServer.Paintable rings;
+
+	String root;
+
+	TreeLayout<String,Integer> layout;
+
+	RadialTreeLayout<String,Integer> radialLayout;
+
+	/**
+	 * the visual component and renderer for the graph
+	 */
+	VisualizationViewer<String,Integer> vv;
+
+    /**
+     * provides a Hyperbolic lens for the view
+     */
+    LensSupport hyperbolicViewSupport;
+    
+    ScalingControl scaler;
+    
+    /**
+     * create an instance of a simple graph with controls to
+     * demo the zoomand hyperbolic features.
+     * 
+     */
+    public RadialTreeLensDemo() {
+        
+        // create a simple graph for the demo
+        // create a simple graph for the demo
+        graph = new DelegateForest<String,Integer>();
+
+        createTree();
+        
+        layout = new TreeLayout<String,Integer>(graph);
+        radialLayout = new RadialTreeLayout<String,Integer>(graph);
+        radialLayout.setSize(new Dimension(600,600));
+
+        Dimension preferredSize = new Dimension(600,600);
+        
+        final VisualizationModel<String,Integer> visualizationModel = 
+            new DefaultVisualizationModel<String,Integer>(radialLayout, preferredSize);
+        vv =  new VisualizationViewer<String,Integer>(visualizationModel, preferredSize);
+
+        PickedState<String> ps = vv.getPickedVertexState();
+        PickedState<Integer> pes = vv.getPickedEdgeState();
+        vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(ps, Color.red, Color.yellow));
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Integer>(pes, Color.black, Color.cyan));
+        vv.setBackground(Color.white);
+        
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph));
+        
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        Container content = getContentPane();
+        GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv);
+        content.add(gzsp);
+        
+        final DefaultModalGraphMouse<String,Integer> graphMouse
+        	= new DefaultModalGraphMouse<String,Integer>();
+
+        vv.setGraphMouse(graphMouse);
+        vv.addKeyListener(graphMouse.getModeKeyListener());
+        rings = new Rings();
+		vv.addPreRenderPaintable(rings);
+
+        hyperbolicViewSupport = 
+            new ViewLensSupport<String,Integer>(vv, new HyperbolicShapeTransformer(vv, 
+            		vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)), 
+                    new ModalLensGraphMouse());
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        final JRadioButton hyperView = new JRadioButton("Hyperbolic View");
+        hyperView.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                hyperbolicViewSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
+            }
+        });
+
+        graphMouse.addItemListener(hyperbolicViewSupport.getGraphMouse().getModeListener());
+        
+        JMenuBar menubar = new JMenuBar();
+        menubar.add(graphMouse.getModeMenu());
+        gzsp.setCorner(menubar);
+
+        JPanel controls = new JPanel();
+        JPanel zoomControls = new JPanel(new GridLayout(2,1));
+        zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        JPanel hyperControls = new JPanel(new GridLayout(3,2));
+        hyperControls.setBorder(BorderFactory.createTitledBorder("Examiner Lens"));
+        zoomControls.add(plus);
+        zoomControls.add(minus);
+        JPanel modeControls = new JPanel(new BorderLayout());
+        modeControls.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
+        modeControls.add(graphMouse.getModeComboBox());
+        hyperControls.add(hyperView);
+        
+        controls.add(zoomControls);
+        controls.add(hyperControls);
+        controls.add(modeControls);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+
+    private void createTree() {
+    	graph.addVertex("V0");
+    	graph.addEdge(edgeFactory.get(), "V0", "V1");
+    	graph.addEdge(edgeFactory.get(), "V0", "V2");
+    	graph.addEdge(edgeFactory.get(), "V1", "V4");
+    	graph.addEdge(edgeFactory.get(), "V2", "V3");
+    	graph.addEdge(edgeFactory.get(), "V2", "V5");
+    	graph.addEdge(edgeFactory.get(), "V4", "V6");
+    	graph.addEdge(edgeFactory.get(), "V4", "V7");
+    	graph.addEdge(edgeFactory.get(), "V3", "V8");
+    	graph.addEdge(edgeFactory.get(), "V6", "V9");
+    	graph.addEdge(edgeFactory.get(), "V4", "V10");
+    	
+       	graph.addVertex("A0");
+       	graph.addEdge(edgeFactory.get(), "A0", "A1");
+       	graph.addEdge(edgeFactory.get(), "A0", "A2");
+       	graph.addEdge(edgeFactory.get(), "A0", "A3");
+       	
+       	graph.addVertex("B0");
+    	graph.addEdge(edgeFactory.get(), "B0", "B1");
+    	graph.addEdge(edgeFactory.get(), "B0", "B2");
+    	graph.addEdge(edgeFactory.get(), "B1", "B4");
+    	graph.addEdge(edgeFactory.get(), "B2", "B3");
+    	graph.addEdge(edgeFactory.get(), "B2", "B5");
+    	graph.addEdge(edgeFactory.get(), "B4", "B6");
+    	graph.addEdge(edgeFactory.get(), "B4", "B7");
+    	graph.addEdge(edgeFactory.get(), "B3", "B8");
+    	graph.addEdge(edgeFactory.get(), "B6", "B9");
+       	
+    }
+
+    class Rings implements VisualizationServer.Paintable {
+    	
+    	Collection<Double> depths;
+    	
+    	public Rings() {
+    		depths = getDepths();
+    	}
+    	
+    	private Collection<Double> getDepths() {
+    		Set<Double> depths = new HashSet<Double>();
+    		Map<String,PolarPoint> polarLocations = radialLayout.getPolarLocations();
+    		for(String v : graph.getVertices()) {
+    			PolarPoint pp = polarLocations.get(v);
+    			depths.add(pp.getRadius());
+    		}
+    		return depths;
+    	}
+
+		public void paint(Graphics g) {
+			g.setColor(Color.gray);
+			Graphics2D g2d = (Graphics2D)g;
+			Point2D center = radialLayout.getCenter();
+
+			Ellipse2D ellipse = new Ellipse2D.Double();
+			for(double d : depths) {
+				ellipse.setFrameFromDiagonal(center.getX()-d, center.getY()-d, 
+						center.getX()+d, center.getY()+d);
+				Shape shape = 
+					vv.getRenderContext().getMultiLayerTransformer().transform(ellipse);
+				g2d.draw(shape);
+			}
+		}
+
+		public boolean useTransform() {
+			return true;
+		}
+    }
+
+    public static void main(String[] args) {
+        JFrame f = new JFrame();
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.getContentPane().add(new RadialTreeLensDemo());
+        f.pack();
+        f.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/SatelliteViewDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/SatelliteViewDemo.java
new file mode 100644
index 0000000..8c03c2d
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/SatelliteViewDemo.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridLayout;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.GeneralPath;
+
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.ToolTipManager;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationModel;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.VisualizationServer.Paintable;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.SatelliteVisualizationViewer;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.renderers.GradientVertexRenderer;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer;
+
+/**
+ * Demonstrates the construction of a graph visualization with a main and 
+ * a satellite view. The satellite
+ * view is smaller, always contains the entire graph, and contains
+ * a lens shape that shows the boundaries of the visible part of the
+ * graph in the main view. Using the mouse, you can pick, translate,
+ * layout-scale, view-scale, rotate, shear, and region-select in either
+ * view. Using the mouse in either window affects only the main view
+ * and the lens shape in the satellite view.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class SatelliteViewDemo<V, E> extends JApplet {
+
+    static final String instructions = 
+        "<html>"+
+        "<b><h2><center>Instructions for Mouse Listeners</center></h2></b>"+
+        "<p>There are two modes, Transforming and Picking."+
+        "<p>The modes are selected with a combo box."+
+        
+        "<p><p><b>Transforming Mode:</b>"+
+        "<ul>"+
+        "<li>Mouse1+drag pans the graph"+
+        "<li>Mouse1+Shift+drag rotates the graph"+
+        "<li>Mouse1+CTRL(or Command)+drag shears the graph"+
+        "</ul>"+
+        
+        "<b>Picking Mode:</b>"+
+        "<ul>"+
+        "<li>Mouse1 on a Vertex selects the vertex"+
+        "<li>Mouse1 elsewhere unselects all Vertices"+
+        "<li>Mouse1+Shift on a Vertex adds/removes Vertex selection"+
+        "<li>Mouse1+drag on a Vertex moves all selected Vertices"+
+        "<li>Mouse1+drag elsewhere selects Vertices in a region"+
+        "<li>Mouse1+Shift+drag adds selection of Vertices in a new region"+
+        "<li>Mouse1+CTRL on a Vertex selects the vertex and centers the display on it"+
+        "</ul>"+
+       "<b>Both Modes:</b>"+
+       "<ul>"+
+        "<li>Mousewheel scales with a crossover value of 1.0.<p>"+
+        "     - scales the graph layout when the combined scale is greater than 1<p>"+
+        "     - scales the graph view when the combined scale is less than 1";
+    
+    JDialog helpDialog;
+    
+    Paintable viewGrid;
+    
+    /**
+     * create an instance of a simple graph in two views with controls to
+     * demo the features.
+     * 
+     */
+    public SatelliteViewDemo() {
+        
+        // create a simple graph for the demo
+        Graph<String, Number> graph = TestGraphs.getOneComponentGraph();
+        
+        // the preferred sizes for the two views
+        Dimension preferredSize1 = new Dimension(600,600);
+        Dimension preferredSize2 = new Dimension(300, 300);
+        
+        // create one layout for the graph
+        FRLayout<String,Number> layout = new FRLayout<String,Number>(graph);
+        layout.setMaxIterations(500);
+        
+        // create one model that both views will share
+        VisualizationModel<String,Number> vm =
+            new DefaultVisualizationModel<String,Number>(layout, preferredSize1);
+        
+        // create 2 views that share the same model
+        final VisualizationViewer<String,Number> vv1 = 
+            new VisualizationViewer<String,Number>(vm, preferredSize1);
+        final SatelliteVisualizationViewer<String,Number> vv2 = 
+            new SatelliteVisualizationViewer<String,Number>(vv1, preferredSize2);
+        vv1.setBackground(Color.white);
+        vv1.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv1.getPickedEdgeState(), Color.black, Color.cyan));
+        vv1.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(vv1.getPickedVertexState(), Color.red, Color.yellow));
+        vv2.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv2.getPickedEdgeState(), Color.black, Color.cyan));
+        vv2.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(vv2.getPickedVertexState(), Color.red, Color.yellow));
+        vv1.getRenderer().setVertexRenderer(new GradientVertexRenderer<String,Number>(Color.red, Color.white, true));
+        vv1.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        vv1.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR);
+        
+        ScalingControl vv2Scaler = new CrossoverScalingControl();
+        vv2.scaleToLayout(vv2Scaler);
+        
+        viewGrid = new ViewGrid(vv2, vv1);
+
+        // add default listener for ToolTips
+        vv1.setVertexToolTipTransformer(new ToStringLabeller());
+        vv2.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        vv2.getRenderContext().setVertexLabelTransformer(vv1.getRenderContext().getVertexLabelTransformer());
+
+        
+        ToolTipManager.sharedInstance().setDismissDelay(10000);
+        
+        Container content = getContentPane();
+        Container panel = new JPanel(new BorderLayout());
+        Container rightPanel = new JPanel(new GridLayout(2,1));
+        
+        GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv1);
+        panel.add(gzsp);
+        rightPanel.add(new JPanel());
+        rightPanel.add(vv2);
+        panel.add(rightPanel, BorderLayout.EAST);
+        
+        helpDialog = new JDialog();
+        helpDialog.getContentPane().add(new JLabel(instructions));
+        
+        // create a GraphMouse for the main view
+        final DefaultModalGraphMouse<String, Number> graphMouse
+        	= new DefaultModalGraphMouse<String, Number>();
+        vv1.setGraphMouse(graphMouse);
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv1, 1.1f, vv1.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv1, 1/1.1f, vv1.getCenter());
+            }
+        });
+        
+        JComboBox<?> modeBox = graphMouse.getModeComboBox();
+        modeBox.addItemListener(((DefaultModalGraphMouse<?, ?>)vv2.getGraphMouse())
+        	.getModeListener());
+        
+        JCheckBox gridBox = new JCheckBox("Show Grid");
+        gridBox.addItemListener(new ItemListener() {
+			public void itemStateChanged(ItemEvent e) {
+				showGrid(vv2, e.getStateChange() == ItemEvent.SELECTED);
+			}});
+        JButton help = new JButton("Help");
+        help.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                helpDialog.pack();
+                helpDialog.setVisible(true);
+            }
+        });
+
+        JPanel controls = new JPanel();
+        controls.add(plus);
+        controls.add(minus);
+        controls.add(modeBox);
+        controls.add(gridBox);
+        controls.add(help);
+        content.add(panel);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+    
+    protected void showGrid(VisualizationViewer<?, ?> vv, boolean state) {
+    		if(state == true) {
+    			vv.addPreRenderPaintable(viewGrid);
+    		} else {
+    			vv.removePreRenderPaintable(viewGrid);
+    		}
+        vv.repaint();
+    }
+    
+    /**
+     * draws a grid on the SatelliteViewer's lens
+     * @author Tom Nelson
+     *
+     */
+    static class ViewGrid implements Paintable {
+
+        VisualizationViewer<?, ?> master;
+        VisualizationViewer<?, ?> vv;
+        
+        public ViewGrid(VisualizationViewer<?, ?> vv, VisualizationViewer<?, ?> master) {
+            this.vv = vv;
+            this.master = master;
+        }
+        public void paint(Graphics g) {
+            ShapeTransformer masterViewTransformer = master.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
+            ShapeTransformer masterLayoutTransformer = master.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+            ShapeTransformer vvLayoutTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+
+            Rectangle rect = master.getBounds();
+            GeneralPath path = new GeneralPath();
+            path.moveTo(rect.x, rect.y);
+            path.lineTo(rect.width,rect.y);
+            path.lineTo(rect.width, rect.height);
+            path.lineTo(rect.x, rect.height);
+            path.lineTo(rect.x, rect.y);
+            
+            for(int i=0; i<=rect.width; i+=rect.width/10) {
+            		path.moveTo(rect.x+i, rect.y);
+            		path.lineTo(rect.x+i, rect.height);
+            }
+            for(int i=0; i<=rect.height; i+=rect.height/10) {
+            		path.moveTo(rect.x, rect.y+i);
+            		path.lineTo(rect.width, rect.y+i);
+            }
+            Shape lens = path;
+            lens = masterViewTransformer.inverseTransform(lens);
+            lens = masterLayoutTransformer.inverseTransform(lens);
+            lens = vvLayoutTransformer.transform(lens);
+            Graphics2D g2d = (Graphics2D)g;
+            Color old = g.getColor();
+            g.setColor(Color.cyan);
+            g2d.draw(lens);
+            
+            path = new GeneralPath();
+            path.moveTo((float)rect.getMinX(), (float)rect.getCenterY());
+            path.lineTo((float)rect.getMaxX(), (float)rect.getCenterY());
+            path.moveTo((float)rect.getCenterX(), (float)rect.getMinY());
+            path.lineTo((float)rect.getCenterX(), (float)rect.getMaxY());
+            Shape crosshairShape = path;
+            crosshairShape = masterViewTransformer.inverseTransform(crosshairShape);
+            crosshairShape = masterLayoutTransformer.inverseTransform(crosshairShape);
+            crosshairShape = vvLayoutTransformer.transform(crosshairShape);
+            g.setColor(Color.black);
+            g2d.setStroke(new BasicStroke(3));
+            g2d.draw(crosshairShape);
+            
+            g.setColor(old);
+        }
+
+        public boolean useTransform() {
+            return true;
+        }
+    }
+
+    
+    public static void main(String[] args) {
+        JFrame f = new JFrame();
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.getContentPane().add(new SatelliteViewDemo<String, Number>());
+        f.pack();
+        f.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/ShortestPathDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/ShortestPathDemo.java
new file mode 100644
index 0000000..d175477
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/ShortestPathDemo.java
@@ -0,0 +1,309 @@
+/*
+ * Created on Jan 2, 2004
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Paint;
+import java.awt.Stroke;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.geom.Point2D;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingConstants;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.generators.random.EppsteinPowerLawGenerator;
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.shortestpath.BFSDistanceLabeler;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+
+/**
+ * Demonstrates use of the shortest path algorithm and visualization of the
+ * results.
+ * 
+ * @author danyelf
+ */
+public class ShortestPathDemo extends JPanel {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 7526217664458188502L;
+
+	/**
+	 * Starting vertex
+	 */
+	private String mFrom;
+
+	/**
+	 * Ending vertex
+	 */	
+	private String mTo;
+	private Graph<String,Number> mGraph;
+	private Set<String> mPred;
+	
+	public ShortestPathDemo() {
+	
+		this.mGraph = getGraph();
+		setBackground(Color.WHITE);
+		// show graph
+        final Layout<String,Number> layout = new FRLayout<String,Number>(mGraph);
+        final VisualizationViewer<String,Number> vv = new VisualizationViewer<String,Number>(layout);
+        vv.setBackground(Color.WHITE);
+
+        vv.getRenderContext().setVertexDrawPaintTransformer(new MyVertexDrawPaintFunction<String>());
+        vv.getRenderContext().setVertexFillPaintTransformer(new MyVertexFillPaintFunction<String>());
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new MyEdgePaintFunction());
+        vv.getRenderContext().setEdgeStrokeTransformer(new MyEdgeStrokeFunction());
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        vv.setGraphMouse(new DefaultModalGraphMouse<String, Number>());
+        vv.addPostRenderPaintable(new VisualizationViewer.Paintable(){
+            
+            public boolean useTransform() {
+                return true;
+            }
+            public void paint(Graphics g) {
+                if(mPred == null) return;
+                
+                // for all edges, paint edges that are in shortest path
+                for (Number e : layout.getGraph().getEdges()) {
+                    
+                    if(isBlessed(e)) {
+                        String v1 = mGraph.getEndpoints(e).getFirst();
+                        String v2 = mGraph.getEndpoints(e).getSecond();
+                        Point2D p1 = layout.apply(v1);
+                        Point2D p2 = layout.apply(v2);
+                        p1 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p1);
+                        p2 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p2);
+                        Renderer<String,Number> renderer = vv.getRenderer();
+                        renderer.renderEdge(
+                                vv.getRenderContext(),
+                                layout,
+                                e);
+                    }
+                }
+            }
+        });
+        
+        setLayout(new BorderLayout());
+        add(vv, BorderLayout.CENTER);
+        // set up controls
+        add(setUpControls(), BorderLayout.SOUTH);
+	}
+
+    boolean isBlessed( Number e ) {
+    	Pair<String> endpoints = mGraph.getEndpoints(e);
+		String v1= endpoints.getFirst()	;
+		String v2= endpoints.getSecond() ;
+		return v1.equals(v2) == false && mPred.contains(v1) && mPred.contains(v2);
+    }
+    
+	/**
+	 * @author danyelf
+	 */
+	public class MyEdgePaintFunction implements Function<Number,Paint> {
+	    
+		public Paint apply(Number e) {
+			if ( mPred == null || mPred.size() == 0) return Color.BLACK;
+			if( isBlessed( e )) {
+				return new Color(0.0f, 0.0f, 1.0f, 0.5f);//Color.BLUE;
+			} else {
+				return Color.LIGHT_GRAY;
+			}
+		}
+	}
+	
+	public class MyEdgeStrokeFunction implements Function<Number,Stroke> {
+        protected final Stroke THIN = new BasicStroke(1);
+        protected final Stroke THICK = new BasicStroke(1);
+
+        public Stroke apply(Number e) {
+			if ( mPred == null || mPred.size() == 0) return THIN;
+			if (isBlessed( e ) ) {
+			    return THICK;
+			} else 
+			    return THIN;
+        }
+	    
+	}
+	
+	/**
+	 * @author danyelf
+	 */
+	public class MyVertexDrawPaintFunction<V> implements Function<V,Paint> {
+
+		public Paint apply(V v) {
+			return Color.black;
+		}
+
+	}
+
+	public class MyVertexFillPaintFunction<V> implements Function<V,Paint> {
+
+		public Paint apply( V v ) {
+			if ( v == mFrom) {
+				return Color.BLUE;
+			}
+			if ( v == mTo ) {
+				return Color.BLUE;
+			}
+			if ( mPred == null ) {
+				return Color.LIGHT_GRAY;
+			} else {
+				if ( mPred.contains(v)) {
+					return Color.RED;
+				} else {
+					return Color.LIGHT_GRAY;
+				}
+			}
+		}
+
+	}
+
+	/**
+	 *  
+	 */
+	private JPanel setUpControls() {
+		JPanel jp = new JPanel();
+		jp.setBackground(Color.WHITE);
+		jp.setLayout(new BoxLayout(jp, BoxLayout.PAGE_AXIS));
+		jp.setBorder(BorderFactory.createLineBorder(Color.black, 3));		
+		jp.add(
+			new JLabel("Select a pair of vertices for which a shortest path will be displayed"));
+		JPanel jp2 = new JPanel();
+		jp2.add(new JLabel("vertex from", SwingConstants.LEFT));
+		jp2.add(getSelectionBox(true));
+		jp2.setBackground(Color.white);
+		JPanel jp3 = new JPanel();
+		jp3.add(new JLabel("vertex to", SwingConstants.LEFT));
+		jp3.add(getSelectionBox(false));
+		jp3.setBackground(Color.white);
+		jp.add( jp2 );
+		jp.add( jp3 );
+		return jp;
+	}
+
+	private Component getSelectionBox(final boolean from) {
+
+		Set<String> s = new TreeSet<String>();
+		
+		for (String v : mGraph.getVertices()) {
+			s.add(v);
+		}
+		final JComboBox<String> choices = new JComboBox<String>((String[]) s.toArray());
+		choices.setSelectedIndex(-1);
+		choices.setBackground(Color.WHITE);
+		choices.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				String v = (String) choices.getSelectedItem();
+					
+				if (from) {
+					mFrom = v;
+				} else {
+					mTo = v;
+				}
+				drawShortest();
+				repaint();				
+			}
+		});
+		return choices;
+	}
+
+	/**
+	 *  
+	 */
+	protected void drawShortest() {
+		if (mFrom == null || mTo == null) {
+			return;
+		}
+		BFSDistanceLabeler<String,Number> bdl = new BFSDistanceLabeler<String,Number>();
+		bdl.labelDistances(mGraph, mFrom);
+		mPred = new HashSet<String>();
+		
+		// grab a predecessor
+		String v = mTo;
+		Set<String> prd = bdl.getPredecessors(v);
+		mPred.add( mTo );
+		while( prd != null && prd.size() > 0) {
+			v = prd.iterator().next();
+			mPred.add( v );
+			if ( v == mFrom ) return;
+			prd = bdl.getPredecessors(v);
+		}
+	}
+
+	public static void main(String[] s) {
+		JFrame jf = new JFrame();
+		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		jf.getContentPane().add(new ShortestPathDemo());
+		jf.pack();
+		jf.setVisible(true);
+	}
+
+	/**
+	 * @return the graph for this demo
+	 */
+	Graph<String,Number> getGraph() {
+
+		Graph<String,Number> g =
+			new EppsteinPowerLawGenerator<String,Number>(
+					new GraphFactory(), new VertexFactory(), new EdgeFactory(), 26, 50, 50).get();
+		Set<String> removeMe = new HashSet<String>();
+		for (String v : g.getVertices()) {
+            if ( g.degree(v) == 0 ) {
+                removeMe.add( v );
+            }
+        }
+		for(String v : removeMe) {
+			g.removeVertex(v);
+		}
+		return g;
+	}
+	
+	static class GraphFactory implements Supplier<Graph<String,Number>> {
+		public Graph<String,Number> get() {
+			return new SparseMultigraph<String,Number>();
+		}
+	}
+	
+	static class VertexFactory implements Supplier<String> {
+		char a = 'a';
+		public String get() {
+			return Character.toString(a++);
+		}
+		
+	}
+	static class EdgeFactory implements Supplier<Number> {
+		int count;
+		public Number get() {
+			return count++;
+		}
+		
+	}
+
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/ShowLayouts.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/ShowLayouts.java
new file mode 100644
index 0000000..4bb44d4
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/ShowLayouts.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JPanel;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.generators.random.MixedRandomGraphGenerator;
+import edu.uci.ics.jung.algorithms.layout.CircleLayout;
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.ISOMLayout;
+import edu.uci.ics.jung.algorithms.layout.KKLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.SpringLayout;
+import edu.uci.ics.jung.algorithms.layout.SpringLayout2;
+import edu.uci.ics.jung.algorithms.layout.util.Relaxer;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.SparseMultigraph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.layout.LayoutTransition;
+import edu.uci.ics.jung.visualization.util.Animator;
+
+
+/**
+ * Demonstrates several of the graph layout algorithms.
+ * Allows the user to interactively select one of several graphs, and one of
+ * several layouts, and visualizes the combination.
+ * 
+ * @author Danyel Fisher
+ * @author Joshua O'Madadhain
+ */
+ at SuppressWarnings("serial")
+public class ShowLayouts extends JApplet {
+    protected static Graph<? extends Object, ? extends Object>[] g_array;
+    protected static int graph_index;
+    protected static String[] graph_names = {"Two component graph", 
+        "Random mixed-mode graph", "Miscellaneous multicomponent graph", 
+        "Random directed acyclic graph", "One component graph", 
+        "Chain+isolate graph", "Trivial (disconnected) graph"};
+    
+	public static class GraphChooser implements ActionListener
+    {
+		private JComboBox<?> layout_combo;
+
+		public GraphChooser(JComboBox<?> layout_combo)
+        {
+            this.layout_combo = layout_combo;
+        }
+        
+        public void actionPerformed(ActionEvent e)
+        {
+			JComboBox<?> cb = (JComboBox<?>)e.getSource();
+            graph_index = cb.getSelectedIndex();
+            layout_combo.setSelectedIndex(layout_combo.getSelectedIndex()); // rebuild the layout
+        }
+    }
+
+    /**
+	 * 
+	 * @author danyelf
+	 */
+	
+	private static final class LayoutChooser implements ActionListener
+    {
+        private final JComboBox<?> jcb;
+        private final VisualizationViewer<Integer,Number> vv;
+
+        private LayoutChooser(JComboBox<?> jcb, VisualizationViewer<Integer,Number> vv)
+        {
+            super();
+            this.jcb = jcb;
+            this.vv = vv;
+        }
+
+        @SuppressWarnings("unchecked")
+		public void actionPerformed(ActionEvent arg0)
+        {
+            Object[] constructorArgs =
+                { g_array[graph_index]};
+
+			Class<? extends Layout<Integer,Number>> layoutC = 
+                (Class<? extends Layout<Integer,Number>>) jcb.getSelectedItem();
+            try
+            {
+                Constructor<? extends Layout<Integer, Number>> constructor = layoutC
+                        .getConstructor(new Class[] {Graph.class});
+                Object o = constructor.newInstance(constructorArgs);
+                Layout<Integer,Number> l = (Layout<Integer,Number>) o;
+                l.setInitializer(vv.getGraphLayout());
+                l.setSize(vv.getSize());
+                
+				LayoutTransition<Integer,Number> lt =
+					new LayoutTransition<Integer,Number>(vv, vv.getGraphLayout(), l);
+				Animator animator = new Animator(lt);
+				animator.start();
+				vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
+				vv.repaint();
+                
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+	private static JPanel getGraphPanel()
+    {
+        g_array = 
+            (Graph<? extends Object,? extends Object>[])
+            new Graph<?,?>[graph_names.length];
+        
+        Supplier<Graph<Integer,Number>> graphFactory =
+    		new Supplier<Graph<Integer,Number>>() {
+    		public Graph<Integer,Number> get() {
+    			return new SparseMultigraph<Integer,Number>();
+    		}
+    	};
+
+    	Supplier<Integer> vertexFactory = new Supplier<Integer>() {
+    			int count;
+				public Integer get() {
+					return count++;
+				}};
+				Supplier<Number> edgeFactory = new Supplier<Number>() {
+			int count;
+				public Number get() {
+					return count++;
+				}};
+
+            
+        g_array[0] = TestGraphs.createTestGraph(false);
+        g_array[1] = MixedRandomGraphGenerator.generateMixedRandomGraph(graphFactory, 
+        		vertexFactory, edgeFactory, new HashMap<Number,Number>(), 20, new HashSet<Integer>());
+        g_array[2] = TestGraphs.getDemoGraph();
+        g_array[3] = TestGraphs.createDirectedAcyclicGraph(4, 4, 0.3);
+        g_array[4] = TestGraphs.getOneComponentGraph();
+        g_array[5] = TestGraphs.createChainPlusIsolates(18, 5);
+        g_array[6] = TestGraphs.createChainPlusIsolates(0, 20);
+
+        Graph<? extends Object, ? extends Object> g = g_array[4]; // initial graph
+
+        final VisualizationViewer<Integer,Number> vv = 
+            new VisualizationViewer<Integer,Number>(new FRLayout(g));
+        
+        vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<Integer>(vv.getPickedVertexState(), Color.red, Color.yellow));
+        
+        final DefaultModalGraphMouse<Integer,Number> graphMouse = new DefaultModalGraphMouse<Integer,Number>();
+        vv.setGraphMouse(graphMouse);
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        JButton reset = new JButton("reset");
+        reset.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				Layout<Integer,Number> layout = vv.getGraphLayout();
+				layout.initialize();
+				Relaxer relaxer = vv.getModel().getRelaxer();
+				if(relaxer != null) {
+//				if(layout instanceof IterativeContext) {
+					relaxer.stop();
+					relaxer.prerelax();
+					relaxer.relax();
+				}
+			}});
+        
+        JComboBox modeBox = graphMouse.getModeComboBox();
+        modeBox.addItemListener(((DefaultModalGraphMouse<Integer,Number>)vv.getGraphMouse()).getModeListener());
+
+        JPanel jp = new JPanel();
+        jp.setBackground(Color.WHITE);
+        jp.setLayout(new BorderLayout());
+        jp.add(vv, BorderLayout.CENTER);
+        Class[] combos = getCombos();
+        final JComboBox jcb = new JComboBox(combos);
+        // use a renderer to shorten the layout name presentation
+        jcb.setRenderer(new DefaultListCellRenderer() {
+            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+                String valueString = value.toString();
+                valueString = valueString.substring(valueString.lastIndexOf('.')+1);
+                return super.getListCellRendererComponent(list, valueString, index, isSelected,
+                        cellHasFocus);
+            }
+        });
+        jcb.addActionListener(new LayoutChooser(jcb, vv));
+        jcb.setSelectedItem(FRLayout.class);
+
+        JPanel control_panel = new JPanel(new GridLayout(2,1));
+        JPanel topControls = new JPanel();
+        JPanel bottomControls = new JPanel();
+        control_panel.add(topControls);
+        control_panel.add(bottomControls);
+        jp.add(control_panel, BorderLayout.NORTH);
+        
+        final JComboBox graph_chooser = new JComboBox(graph_names);
+        
+        graph_chooser.addActionListener(new GraphChooser(jcb));
+        
+        topControls.add(jcb);
+        topControls.add(graph_chooser);
+        bottomControls.add(plus);
+        bottomControls.add(minus);
+        bottomControls.add(modeBox);
+        bottomControls.add(reset);
+        return jp;
+    }
+
+    public void start()
+    {
+        this.getContentPane().add(getGraphPanel());
+    }
+
+    /**
+     * @return
+     */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private static Class<? extends Layout>[] getCombos()
+    {
+        List<Class<? extends Layout>> layouts = new ArrayList<Class<? extends Layout>>();
+        layouts.add(KKLayout.class);
+        layouts.add(FRLayout.class);
+        layouts.add(CircleLayout.class);
+        layouts.add(SpringLayout.class);
+        layouts.add(SpringLayout2.class);
+        layouts.add(ISOMLayout.class);
+        return layouts.toArray(new Class[0]);
+    }
+
+    public static void main(String[] args)
+    {
+        JPanel jp = getGraphPanel();
+
+        JFrame jf = new JFrame();
+        jf.getContentPane().add(jp);
+        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        jf.pack();
+        jf.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/SimpleGraphDraw.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/SimpleGraphDraw.java
new file mode 100644
index 0000000..a7550d6
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/SimpleGraphDraw.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2008, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.samples;
+
+import java.io.IOException;
+
+import javax.swing.JFrame;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedSparseGraph;
+import edu.uci.ics.jung.io.PajekNetReader;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+
+/**
+ * A class that shows the minimal work necessary to load and visualize a graph.
+ */
+public class SimpleGraphDraw 
+{
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+	public static void main(String[] args) throws IOException 
+    {
+        JFrame jf = new JFrame();
+		Graph g = getGraph();
+        VisualizationViewer vv = new VisualizationViewer(new FRLayout(g));
+        jf.getContentPane().add(vv);
+        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        jf.pack();
+        jf.setVisible(true);
+    }
+    
+    /**
+     * Generates a graph: in this case, reads it from the file
+     * "samples/datasetsgraph/simple.net"
+     * @return A sample undirected graph
+     * @throws IOException if there is an error in reading the file
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+	public static Graph getGraph() throws IOException 
+    {
+        PajekNetReader pnr = new PajekNetReader(new Supplier(){
+			public Object get() {
+				return new Object();
+			}});
+        Graph g = new UndirectedSparseGraph();
+        
+        pnr.load("src/main/resources/datasets/simple.net", g);
+        return g;
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/SubLayoutDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/SubLayoutDemo.java
new file mode 100644
index 0000000..a343775
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/SubLayoutDemo.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.Point2D;
+import java.lang.reflect.Constructor;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import edu.uci.ics.jung.algorithms.layout.AggregateLayout;
+import edu.uci.ics.jung.algorithms.layout.CircleLayout;
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.KKLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.SpringLayout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationModel;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+
+/**
+ * Demonstrates the AggregateLayout
+ * class. In this demo, vertices are visually clustered as they
+ * are selected. The cluster is formed in a new Layout centered at the
+ * middle locations of the selected vertices. The size and layout
+ * algorithm for each new cluster is selectable.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class SubLayoutDemo extends JApplet {
+
+    String instructions =
+        "<html>"+
+        "Use the Layout combobox to select the "+
+        "<p>underlying layout."+
+        "<p>Use the SubLayout combobox to select "+
+        "<p>the type of layout for any clusters you create."+
+        "<p>To create clusters, use the mouse to select "+
+        "<p>multiple vertices, either by dragging a region, "+
+        "<p>or by shift-clicking on multiple vertices."+
+        "<p>After you select vertices, use the "+
+        "<p>Cluster Picked button to cluster them using the "+
+        "<p>layout and size specified in the Sublayout comboboxen."+
+        "<p>Use the Uncluster All button to remove all"+
+        "<p>clusters."+
+        "<p>You can drag the cluster with the mouse." +
+        "<p>Use the 'Picking'/'Transforming' combo-box to switch"+
+        "<p>between picking and transforming mode.</html>";
+    /**
+     * the graph
+     */
+    Graph<String,Number> graph;
+    
+    Map<Graph<String,Number>,Dimension> sizes = new HashMap<Graph<String,Number>,Dimension>();
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+	Class<Layout>[] layoutClasses = new Class[] {
+		CircleLayout.class,SpringLayout.class,FRLayout.class,KKLayout.class
+    };
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<String,Number> vv;
+
+    AggregateLayout<String,Number> clusteringLayout;
+    
+    Dimension subLayoutSize;
+    
+    PickedState<String> ps;
+    
+    @SuppressWarnings("rawtypes")
+	Class<CircleLayout> subLayoutType = CircleLayout.class;
+    
+    /**
+     * create an instance of a simple graph with controls to
+     * demo the zoomand hyperbolic features.
+     * 
+     */
+    public SubLayoutDemo() {
+        
+        // create a simple graph for the demo
+        graph = TestGraphs.getOneComponentGraph();
+
+        // ClusteringLayout is a decorator class that delegates
+        // to another layout, but can also sepately manage the
+        // layout of sub-sets of vertices in circular clusters.
+        clusteringLayout = new AggregateLayout<String,Number>(new FRLayout<String,Number>(graph));
+        	//new SubLayoutDecorator<String,Number>(new FRLayout<String,Number>(graph));
+
+        Dimension preferredSize = new Dimension(600,600);
+        final VisualizationModel<String,Number> visualizationModel = 
+            new DefaultVisualizationModel<String,Number>(clusteringLayout, preferredSize);
+        vv =  new VisualizationViewer<String,Number>(visualizationModel, preferredSize);
+        
+        ps = vv.getPickedVertexState();
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv.getPickedEdgeState(), Color.black, Color.red));
+        vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(vv.getPickedVertexState(), 
+                Color.red, Color.yellow));
+        vv.setBackground(Color.white);
+        
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        /**
+         * the regular graph mouse for the normal view
+         */
+        final DefaultModalGraphMouse<?, ?> graphMouse = new DefaultModalGraphMouse<Object, Object>();
+
+        vv.setGraphMouse(graphMouse);
+        
+        Container content = getContentPane();
+        GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv);
+        content.add(gzsp);
+        
+        JComboBox<?> modeBox = graphMouse.getModeComboBox();
+        modeBox.addItemListener(graphMouse.getModeListener());
+        graphMouse.setMode(ModalGraphMouse.Mode.PICKING);
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        JButton cluster = new JButton("Cluster Picked");
+        cluster.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				clusterPicked();
+			}});
+        
+        JButton uncluster = new JButton("UnCluster All");
+        uncluster.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				uncluster();
+			}});
+        
+        JComboBox<?> layoutTypeComboBox = new JComboBox<Object>(layoutClasses);
+        layoutTypeComboBox.setRenderer(new DefaultListCellRenderer() {
+            public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+                String valueString = value.toString();
+                valueString = valueString.substring(valueString.lastIndexOf('.')+1);
+                return super.getListCellRendererComponent(list, valueString, index, isSelected,
+                        cellHasFocus);
+            }
+        });
+        layoutTypeComboBox.setSelectedItem(FRLayout.class);
+        layoutTypeComboBox.addItemListener(new ItemListener() {
+
+			public void itemStateChanged(ItemEvent e) {
+				if(e.getStateChange() == ItemEvent.SELECTED) {
+					@SuppressWarnings({ "unchecked", "rawtypes" })
+					Class<CircleLayout> clazz = (Class<CircleLayout>)e.getItem();
+					try {
+						Layout<String,Number> layout = getLayoutFor(clazz, graph);
+						layout.setInitializer(vv.getGraphLayout());
+						clusteringLayout.setDelegate(layout);
+						vv.setGraphLayout(clusteringLayout);
+					} catch(Exception ex) {
+						ex.printStackTrace();
+					}
+				}
+			}});
+        
+        JComboBox<?> subLayoutTypeComboBox = new JComboBox<Object>(layoutClasses);
+        
+        subLayoutTypeComboBox.setRenderer(new DefaultListCellRenderer() {
+            public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+                String valueString = value.toString();
+                valueString = valueString.substring(valueString.lastIndexOf('.')+1);
+                return super.getListCellRendererComponent(list, valueString, index, isSelected,
+                        cellHasFocus);
+            }
+        });
+        subLayoutTypeComboBox.addItemListener(new ItemListener() {
+
+			@SuppressWarnings({ "unchecked", "rawtypes" })
+			public void itemStateChanged(ItemEvent e) {
+				if(e.getStateChange() == ItemEvent.SELECTED) {
+					subLayoutType = (Class<CircleLayout>)e.getItem();
+				}
+			}});
+        
+        JComboBox<?> subLayoutDimensionComboBox = 
+        	new JComboBox<Object>(new Dimension[]{
+        			new Dimension(75,75),
+        			new Dimension(100,100),
+        			new Dimension(150,150),
+        			new Dimension(200,200),
+        			new Dimension(250,250),
+        			new Dimension(300,300)
+        	}	
+        	);
+        subLayoutDimensionComboBox.setRenderer(new DefaultListCellRenderer() {
+            public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+                String valueString = value.toString();
+                valueString = valueString.substring(valueString.lastIndexOf('['));
+                valueString = valueString.replaceAll("idth", "");
+                valueString = valueString.replaceAll("eight","");
+                return super.getListCellRendererComponent(list, valueString, index, isSelected,
+                        cellHasFocus);
+            }
+        });
+        subLayoutDimensionComboBox.addItemListener(new ItemListener() {
+
+			public void itemStateChanged(ItemEvent e) {
+				if(e.getStateChange() == ItemEvent.SELECTED) {
+					subLayoutSize = (Dimension)e.getItem();
+				}
+			}});
+        subLayoutDimensionComboBox.setSelectedIndex(1);
+
+        JButton help = new JButton("Help");
+        help.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                JOptionPane.showMessageDialog((JComponent)e.getSource(), instructions, "Help", JOptionPane.PLAIN_MESSAGE);
+            }
+        });
+        Dimension space = new Dimension(20,20);
+        Box controls = Box.createVerticalBox();
+        controls.add(Box.createRigidArea(space));
+        
+        JPanel zoomControls = new JPanel(new GridLayout(1,2));
+        zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        zoomControls.add(plus);
+        zoomControls.add(minus);
+        heightConstrain(zoomControls);
+        controls.add(zoomControls);
+        controls.add(Box.createRigidArea(space));
+        
+        JPanel clusterControls = new JPanel(new GridLayout(0,1));
+        clusterControls.setBorder(BorderFactory.createTitledBorder("Clustering"));
+        clusterControls.add(cluster);
+        clusterControls.add(uncluster);
+        heightConstrain(clusterControls);
+        controls.add(clusterControls);
+        controls.add(Box.createRigidArea(space));
+        
+        JPanel layoutControls = new JPanel(new GridLayout(0,1));
+        layoutControls.setBorder(BorderFactory.createTitledBorder("Layout"));
+        layoutControls.add(layoutTypeComboBox);
+        heightConstrain(layoutControls);
+        controls.add(layoutControls);
+
+        JPanel subLayoutControls = new JPanel(new GridLayout(0,1));
+        subLayoutControls.setBorder(BorderFactory.createTitledBorder("SubLayout"));
+        subLayoutControls.add(subLayoutTypeComboBox);
+        subLayoutControls.add(subLayoutDimensionComboBox);
+        heightConstrain(subLayoutControls);
+        controls.add(subLayoutControls);
+        controls.add(Box.createRigidArea(space));
+        
+        JPanel modePanel = new JPanel(new GridLayout(1,1));
+        modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
+        modePanel.add(modeBox);
+        heightConstrain(modePanel);
+        controls.add(modePanel);
+        controls.add(Box.createRigidArea(space));
+
+        controls.add(help);
+        controls.add(Box.createVerticalGlue());
+        content.add(controls, BorderLayout.EAST);
+    }
+    
+    private void heightConstrain(Component component) {
+    	Dimension d = new Dimension(component.getMaximumSize().width,
+    			component.getMinimumSize().height);
+    	component.setMaximumSize(d);
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+	private Layout<String, Number> getLayoutFor(Class<CircleLayout> layoutClass, Graph<String, Number> graph) throws Exception {
+    	Object[] args = new Object[]{graph};
+    	Constructor<CircleLayout> constructor = layoutClass.getConstructor(new Class[] {Graph.class});
+    	return  constructor.newInstance(args);
+    }
+    
+    private void clusterPicked() {
+    	cluster(true);
+    }
+    
+    private void uncluster() {
+    	cluster(false);
+    }
+
+    @SuppressWarnings("unchecked")
+	private void cluster(boolean state) {
+    	if(state == true) {
+    		// put the picked vertices into a new sublayout 
+    		Collection<String> picked = ps.getPicked();
+    		if(picked.size() > 1) {
+    			Point2D center = new Point2D.Double();
+    			double x = 0;
+    			double y = 0;
+    			for(String vertex : picked) {
+    				Point2D p = clusteringLayout.apply(vertex);
+    				x += p.getX();
+    				y += p.getY();
+    			}
+    			x /= picked.size();
+    			y /= picked.size();
+				center.setLocation(x,y);
+
+    			Graph<String, Number> subGraph;
+    			try {
+    				subGraph = graph.getClass().newInstance();
+    				for(String vertex : picked) {
+    					subGraph.addVertex(vertex);
+    					Collection<Number> incidentEdges = graph.getIncidentEdges(vertex);
+    					for(Number edge : incidentEdges) {
+    						Pair<String> endpoints = graph.getEndpoints(edge);
+    						if(picked.containsAll(endpoints)) {
+    							// put this edge into the subgraph
+    							subGraph.addEdge(edge, endpoints.getFirst(), endpoints.getSecond());
+    						}
+    					}
+    				}
+
+    				Layout<String,Number> subLayout = getLayoutFor(subLayoutType, subGraph);
+    				subLayout.setInitializer(vv.getGraphLayout());
+    				subLayout.setSize(subLayoutSize);
+    				clusteringLayout.put(subLayout,center);
+    				vv.setGraphLayout(clusteringLayout);
+
+    			} catch (Exception e) {
+    				e.printStackTrace();
+    			}
+    		}
+    	} else {
+    		// remove all sublayouts
+    		this.clusteringLayout.removeAll();
+    		vv.setGraphLayout(clusteringLayout);
+    	}
+    }
+
+    public static void main(String[] args) {
+        JFrame f = new JFrame();
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.getContentPane().add(new SubLayoutDemo());
+        f.pack();
+        f.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/TreeCollapseDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/TreeCollapseDemo.java
new file mode 100644
index 0000000..5651ffd
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/TreeCollapseDemo.java
@@ -0,0 +1,384 @@
+package edu.uci.ics.jung.samples;
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridLayout;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JToggleButton;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.PolarPoint;
+import edu.uci.ics.jung.algorithms.layout.RadialTreeLayout;
+import edu.uci.ics.jung.algorithms.layout.TreeLayout;
+import edu.uci.ics.jung.graph.DelegateForest;
+import edu.uci.ics.jung.graph.DelegateTree;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Forest;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.Tree;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.EdgeShape;
+import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.subLayout.TreeCollapser;
+
+/**
+ * Demonstrates "collapsing"/"expanding" of a tree's subtrees.
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class TreeCollapseDemo extends JApplet {
+
+    /**
+     * the graph
+     */
+    Forest<String,Integer> graph;
+
+    Supplier<DirectedGraph<String,Integer>> graphFactory = 
+    	new Supplier<DirectedGraph<String,Integer>>() {
+
+			public DirectedGraph<String, Integer> get() {
+				return new DirectedSparseMultigraph<String,Integer>();
+			}
+		};
+			
+        Supplier<Tree<String,Integer>> treeFactory =
+		new Supplier<Tree<String,Integer>> () {
+
+		public Tree<String, Integer> get() {
+			return new DelegateTree<String,Integer>(graphFactory);
+		}
+	};
+	
+	
+	
+	Supplier<Integer> edgeFactory = new Supplier<Integer>() {
+		int i=0;
+		public Integer get() {
+			return i++;
+		}};
+
+    Supplier<String> vertexFactory = new Supplier<String>() {
+    	int i=0;
+		public String get() {
+			return "V"+i++;
+		}};
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<String,Integer> vv;
+
+    VisualizationServer.Paintable rings;
+
+    String root;
+
+    TreeLayout<String,Integer> layout;
+	FRLayout<?, ?> layout1;
+
+    TreeCollapser collapser;
+
+    RadialTreeLayout<String,Integer> radialLayout;
+
+	public TreeCollapseDemo() {
+
+        // create a simple graph for the demo
+        graph = new DelegateForest<String,Integer>();
+
+        createTree();
+
+        layout = new TreeLayout<String,Integer>(graph);
+        collapser = new TreeCollapser();
+
+        radialLayout = new RadialTreeLayout<String,Integer>(graph);
+        radialLayout.setSize(new Dimension(600,600));
+        vv =  new VisualizationViewer<String,Integer>(layout, new Dimension(600,600));
+        vv.setBackground(Color.white);
+        vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph));
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        vv.getRenderContext().setVertexShapeTransformer(new ClusterVertexShapeFunction<String>());
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        vv.getRenderContext().setArrowFillPaintTransformer(Functions.<Paint>constant(Color.lightGray));
+        rings = new Rings();
+
+        Container content = getContentPane();
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        content.add(panel);
+
+        final DefaultModalGraphMouse<String, Integer> graphMouse
+        	= new DefaultModalGraphMouse<String, Integer>();
+
+        vv.setGraphMouse(graphMouse);
+
+        JComboBox<?> modeBox = graphMouse.getModeComboBox();
+        modeBox.addItemListener(graphMouse.getModeListener());
+        graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING);
+
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+
+        JToggleButton radial = new JToggleButton("Radial");
+        radial.addItemListener(new ItemListener() {
+
+			public void itemStateChanged(ItemEvent e) {
+				if(e.getStateChange() == ItemEvent.SELECTED) {
+					vv.setGraphLayout(radialLayout);
+					vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
+					vv.addPreRenderPaintable(rings);
+				} else {
+					vv.setGraphLayout(layout);
+					vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
+					vv.removePreRenderPaintable(rings);
+				}
+				vv.repaint();
+			}});
+
+        JButton collapse = new JButton("Collapse");
+        collapse.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                Collection<String> picked
+                	= new HashSet<String>(vv.getPickedVertexState().getPicked());
+                if(picked.size() == 1) {
+                	Object root = picked.iterator().next();
+                    Forest<String, Integer> inGraph = (Forest<String, Integer>)layout.getGraph();
+
+                    try {
+						collapser.collapse(vv.getGraphLayout(), inGraph, root);
+					} catch (InstantiationException e1) {
+						// TODO Auto-generated catch block
+						e1.printStackTrace();
+					} catch (IllegalAccessException e1) {
+						// TODO Auto-generated catch block
+						e1.printStackTrace();
+					}
+
+                    vv.getPickedVertexState().clear();
+                    vv.repaint();
+                }
+            }});
+
+        JButton expand = new JButton("Expand");
+        expand.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                Collection<String> picked = vv.getPickedVertexState().getPicked();
+                for(Object v : picked) {
+                    if(v instanceof Forest) {
+                        Forest<String, Integer> inGraph
+                        	= (Forest<String, Integer>)layout.getGraph();
+            			collapser.expand(inGraph, (Forest<?, ?>)v);
+                    }
+                    vv.getPickedVertexState().clear();
+                   vv.repaint();
+                }
+            }});
+
+        JPanel scaleGrid = new JPanel(new GridLayout(1,0));
+        scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom"));
+
+        JPanel controls = new JPanel();
+        scaleGrid.add(plus);
+        scaleGrid.add(minus);
+        controls.add(radial);
+        controls.add(scaleGrid);
+        controls.add(modeBox);
+        controls.add(collapse);
+        controls.add(expand);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+
+    class Rings implements VisualizationServer.Paintable {
+    	
+    	Collection<Double> depths;
+    	
+    	public Rings() {
+    		depths = getDepths();
+    	}
+    	
+    	private Collection<Double> getDepths() {
+    		Set<Double> depths = new HashSet<Double>();
+    		Map<String,PolarPoint> polarLocations = radialLayout.getPolarLocations();
+    		for(String v : graph.getVertices()) {
+    			PolarPoint pp = polarLocations.get(v);
+    			depths.add(pp.getRadius());
+    		}
+    		return depths;
+    	}
+
+		public void paint(Graphics g) {
+			g.setColor(Color.lightGray);
+		
+			Graphics2D g2d = (Graphics2D)g;
+			Point2D center = radialLayout.getCenter();
+
+			Ellipse2D ellipse = new Ellipse2D.Double();
+			for(double d : depths) {
+				ellipse.setFrameFromDiagonal(center.getX()-d, center.getY()-d, 
+						center.getX()+d, center.getY()+d);
+				Shape shape = vv.getRenderContext().
+						getMultiLayerTransformer().getTransformer(Layer.LAYOUT).transform(ellipse);
+				g2d.draw(shape);
+			}
+		}
+
+		public boolean useTransform() {
+			return true;
+		}
+    }
+
+    /**
+     * 
+     */
+    private void createTree() {
+    	graph.addVertex("V0");
+    	graph.addEdge(edgeFactory.get(), "V0", "V1");
+    	graph.addEdge(edgeFactory.get(), "V0", "V2");
+    	graph.addEdge(edgeFactory.get(), "V1", "V4");
+    	graph.addEdge(edgeFactory.get(), "V2", "V3");
+    	graph.addEdge(edgeFactory.get(), "V2", "V5");
+    	graph.addEdge(edgeFactory.get(), "V4", "V6");
+    	graph.addEdge(edgeFactory.get(), "V4", "V7");
+    	graph.addEdge(edgeFactory.get(), "V3", "V8");
+    	graph.addEdge(edgeFactory.get(), "V6", "V9");
+    	graph.addEdge(edgeFactory.get(), "V4", "V10");
+    	
+       	graph.addVertex("A0");
+       	graph.addEdge(edgeFactory.get(), "A0", "A1");
+       	graph.addEdge(edgeFactory.get(), "A0", "A2");
+       	graph.addEdge(edgeFactory.get(), "A0", "A3");
+       	
+       	graph.addVertex("B0");
+    	graph.addEdge(edgeFactory.get(), "B0", "B1");
+    	graph.addEdge(edgeFactory.get(), "B0", "B2");
+    	graph.addEdge(edgeFactory.get(), "B1", "B4");
+    	graph.addEdge(edgeFactory.get(), "B2", "B3");
+    	graph.addEdge(edgeFactory.get(), "B2", "B5");
+    	graph.addEdge(edgeFactory.get(), "B4", "B6");
+    	graph.addEdge(edgeFactory.get(), "B4", "B7");
+    	graph.addEdge(edgeFactory.get(), "B3", "B8");
+    	graph.addEdge(edgeFactory.get(), "B6", "B9");
+       	
+    }
+
+        /**
+     * a demo class that will create a vertex shape that is either a
+     * polygon or star. The number of sides corresponds to the number
+     * of vertices that were collapsed into the vertex represented by
+     * this shape.
+     * 
+     * @author Tom Nelson
+     *
+     * @param <V> the vertex type
+     */
+    class ClusterVertexShapeFunction<V> extends EllipseVertexShapeTransformer<V>
+{
+
+        ClusterVertexShapeFunction() {
+            setSizeTransformer(new ClusterVertexSizeFunction<V>(20));
+        }
+		@Override
+        public Shape apply(V v) {
+            if(v instanceof Graph) {
+                @SuppressWarnings("rawtypes")
+				int size = ((Graph)v).getVertexCount();
+                if (size < 8) {   
+                    int sides = Math.max(size, 3);
+                    return factory.getRegularPolygon(v, sides);
+                }
+                else {
+                    return factory.getRegularStar(v, size);
+                }
+            }
+            return super.apply(v);
+        }
+    }
+
+    /**
+     * A demo class that will make vertices larger if they represent
+     * a collapsed collection of original vertices
+     * @author Tom Nelson
+     *
+     * @param <V> the vertex type
+     */
+    class ClusterVertexSizeFunction<V> implements Function<V,Integer> {
+    	int size;
+        public ClusterVertexSizeFunction(Integer size) {
+            this.size = size;
+        }
+
+        public Integer apply(V v) {
+            if(v instanceof Graph) {
+                return 30;
+            }
+            return size;
+        }
+    }
+
+    public static void main(String[] args) {
+        JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+        content.add(new TreeCollapseDemo());
+        frame.pack();
+        frame.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/TreeLayoutDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/TreeLayoutDemo.java
new file mode 100644
index 0000000..83a914c
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/TreeLayoutDemo.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridLayout;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JToggleButton;
+
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.layout.PolarPoint;
+import edu.uci.ics.jung.algorithms.layout.RadialTreeLayout;
+import edu.uci.ics.jung.algorithms.layout.TreeLayout;
+import edu.uci.ics.jung.graph.DelegateForest;
+import edu.uci.ics.jung.graph.DelegateTree;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Forest;
+import edu.uci.ics.jung.graph.Tree;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse.Mode;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.EdgeShape;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.layout.LayoutTransition;
+import edu.uci.ics.jung.visualization.util.Animator;
+
+/**
+ * Demonsrates TreeLayout and RadialTreeLayout.
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class TreeLayoutDemo extends JApplet {
+
+    /**
+     * the graph
+     */
+    Forest<String,Integer> graph;
+    
+    Supplier<DirectedGraph<String,Integer>> graphFactory = 
+    	new Supplier<DirectedGraph<String,Integer>>() {
+
+			public DirectedGraph<String, Integer> get() {
+				return new DirectedSparseMultigraph<String,Integer>();
+			}
+		};
+			
+		Supplier<Tree<String,Integer>> treeFactory =
+		new Supplier<Tree<String,Integer>> () {
+
+		public Tree<String, Integer> get() {
+			return new DelegateTree<String,Integer>(graphFactory);
+		}
+	};
+	
+	Supplier<Integer> edgeFactory = new Supplier<Integer>() {
+		int i=0;
+		public Integer get() {
+			return i++;
+		}};
+    
+    Supplier<String> vertexFactory = new Supplier<String>() {
+    	int i=0;
+		public String get() {
+			return "V"+i++;
+		}};
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<String,Integer> vv;
+    
+    VisualizationServer.Paintable rings;
+    
+    String root;
+    
+    TreeLayout<String,Integer> treeLayout;
+    
+    RadialTreeLayout<String,Integer> radialLayout;
+
+    public TreeLayoutDemo() {
+        
+        // create a simple graph for the demo
+        graph = new DelegateForest<String,Integer>();
+
+        createTree();
+        
+        treeLayout = new TreeLayout<String,Integer>(graph);
+        radialLayout = new RadialTreeLayout<String,Integer>(graph);
+        radialLayout.setSize(new Dimension(600,600));
+        vv =  new VisualizationViewer<String,Integer>(treeLayout, new Dimension(600,600));
+        vv.setBackground(Color.white);
+        vv.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(graph));
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        vv.getRenderContext().setArrowFillPaintTransformer(Functions.<Paint>constant(Color.lightGray));
+        rings = new Rings();
+
+        Container content = getContentPane();
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        content.add(panel);
+        
+        final DefaultModalGraphMouse<String, Integer> graphMouse
+        	= new DefaultModalGraphMouse<String, Integer>();
+
+        vv.setGraphMouse(graphMouse);
+        
+        JComboBox<Mode> modeBox = graphMouse.getModeComboBox();
+        modeBox.addItemListener(graphMouse.getModeListener());
+        graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING);
+
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        JToggleButton radial = new JToggleButton("Radial");
+        radial.addItemListener(new ItemListener() {
+
+			public void itemStateChanged(ItemEvent e) {
+				if(e.getStateChange() == ItemEvent.SELECTED) {
+					
+					LayoutTransition<String,Integer> lt =
+						new LayoutTransition<String,Integer>(vv, treeLayout, radialLayout);
+					Animator animator = new Animator(lt);
+					animator.start();
+					vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
+					vv.addPreRenderPaintable(rings);
+				} else {
+					LayoutTransition<String,Integer> lt =
+						new LayoutTransition<String,Integer>(vv, radialLayout, treeLayout);
+					Animator animator = new Animator(lt);
+					animator.start();
+					vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
+					vv.removePreRenderPaintable(rings);
+				}
+				vv.repaint();
+			}});
+
+        JPanel scaleGrid = new JPanel(new GridLayout(1,0));
+        scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom"));
+
+        JPanel controls = new JPanel();
+        scaleGrid.add(plus);
+        scaleGrid.add(minus);
+        controls.add(radial);
+        controls.add(scaleGrid);
+        controls.add(modeBox);
+
+        content.add(controls, BorderLayout.SOUTH);
+    }
+    
+    class Rings implements VisualizationServer.Paintable {
+    	
+    	Collection<Double> depths;
+    	
+    	public Rings() {
+    		depths = getDepths();
+    	}
+    	
+    	private Collection<Double> getDepths() {
+    		Set<Double> depths = new HashSet<Double>();
+    		Map<String,PolarPoint> polarLocations = radialLayout.getPolarLocations();
+    		for(String v : graph.getVertices()) {
+    			PolarPoint pp = polarLocations.get(v);
+    			depths.add(pp.getRadius());
+    		}
+    		return depths;
+    	}
+
+		public void paint(Graphics g) {
+			g.setColor(Color.lightGray);
+		
+			Graphics2D g2d = (Graphics2D)g;
+			Point2D center = radialLayout.getCenter();
+
+			Ellipse2D ellipse = new Ellipse2D.Double();
+			for(double d : depths) {
+				ellipse.setFrameFromDiagonal(center.getX()-d, center.getY()-d, 
+						center.getX()+d, center.getY()+d);
+				Shape shape = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).transform(ellipse);
+				g2d.draw(shape);
+			}
+		}
+
+		public boolean useTransform() {
+			return true;
+		}
+    }
+    
+    /**
+     * 
+     */
+    private void createTree() {
+    	graph.addVertex("V0");
+    	graph.addEdge(edgeFactory.get(), "V0", "V1");
+    	graph.addEdge(edgeFactory.get(), "V0", "V2");
+    	graph.addEdge(edgeFactory.get(), "V1", "V4");
+    	graph.addEdge(edgeFactory.get(), "V2", "V3");
+    	graph.addEdge(edgeFactory.get(), "V2", "V5");
+    	graph.addEdge(edgeFactory.get(), "V4", "V6");
+    	graph.addEdge(edgeFactory.get(), "V4", "V7");
+    	graph.addEdge(edgeFactory.get(), "V3", "V8");
+    	graph.addEdge(edgeFactory.get(), "V6", "V9");
+    	graph.addEdge(edgeFactory.get(), "V4", "V10");
+    	
+       	graph.addVertex("A0");
+       	graph.addEdge(edgeFactory.get(), "A0", "A1");
+       	graph.addEdge(edgeFactory.get(), "A0", "A2");
+       	graph.addEdge(edgeFactory.get(), "A0", "A3");
+       	
+       	graph.addVertex("B0");
+    	graph.addEdge(edgeFactory.get(), "B0", "B1");
+    	graph.addEdge(edgeFactory.get(), "B0", "B2");
+    	graph.addEdge(edgeFactory.get(), "B1", "B4");
+    	graph.addEdge(edgeFactory.get(), "B2", "B3");
+    	graph.addEdge(edgeFactory.get(), "B2", "B5");
+    	graph.addEdge(edgeFactory.get(), "B4", "B6");
+    	graph.addEdge(edgeFactory.get(), "B4", "B7");
+    	graph.addEdge(edgeFactory.get(), "B3", "B8");
+    	graph.addEdge(edgeFactory.get(), "B6", "B9");
+       	
+    }
+
+    public static void main(String[] args) {
+        JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+        content.add(new TreeLayoutDemo());
+        frame.pack();
+        frame.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/TwoModelDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/TwoModelDemo.java
new file mode 100644
index 0000000..3c7a3b9
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/TwoModelDemo.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.ISOMLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationModel;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.picking.MultiPickedState;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/**
+ * Demonstrates a single graph with 2 layouts in 2 views.
+ * They share picking, transforms, and a pluggable renderer
+ * 
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class TwoModelDemo extends JApplet {
+
+     /**
+     * the graph
+     */
+    Graph<String,Number> graph;
+
+    /**
+     * the visual components and renderers for the graph
+     */
+    VisualizationViewer<String,Number> vv1;
+    VisualizationViewer<String,Number> vv2;
+    
+    /**
+     * the normal Function
+     */
+    MutableTransformer layoutTransformer;
+    
+    Dimension preferredSize = new Dimension(300,300);
+    
+    /**
+     * create an instance of a simple graph in two views with controls to
+     * demo the zoom features.
+     * 
+     */
+    public TwoModelDemo() {
+        
+        // create a simple graph for the demo
+        // both models will share one graph
+        graph = TestGraphs.getOneComponentGraph();
+        
+        // create two layouts for the one graph, one layout for each model
+        Layout<String,Number> layout1 = new FRLayout<String,Number>(graph);
+        Layout<String,Number> layout2 = new ISOMLayout<String,Number>(graph);
+
+        // create the two models, each with a different layout
+        VisualizationModel<String,Number> vm1 =
+            new DefaultVisualizationModel<String,Number>(layout1, preferredSize);
+        VisualizationModel<String,Number> vm2 = 
+            new DefaultVisualizationModel<String,Number>(layout2, preferredSize);
+
+        // create the two views, one for each model
+        // they share the same renderer
+        vv1 = new VisualizationViewer<String,Number>(vm1, preferredSize);
+        vv2 = new VisualizationViewer<String,Number>(vm2, preferredSize);
+        vv1.setRenderContext(vv2.getRenderContext());
+        
+        // share the model Function between the two models
+//        layoutTransformer = vv1.getLayoutTransformer();
+//        vv2.setLayoutTransformer(layoutTransformer);
+//        
+//        // share the view Function between the two models
+//        vv2.setViewTransformer(vv1.getViewTransformer());
+        
+        vv2.getRenderContext().setMultiLayerTransformer(vv1.getRenderContext().getMultiLayerTransformer());
+        vv2.getRenderContext().getMultiLayerTransformer().addChangeListener(vv1);
+
+        vv1.setBackground(Color.white);
+        vv2.setBackground(Color.white);
+        
+        // share one PickedState between the two views
+        PickedState<String> ps = new MultiPickedState<String>();
+        vv1.setPickedVertexState(ps);
+        vv2.setPickedVertexState(ps);
+        PickedState<Number> pes = new MultiPickedState<Number>();
+        vv1.setPickedEdgeState(pes);
+        vv2.setPickedEdgeState(pes);
+        
+        // set an edge paint function that will show picking for edges
+        vv1.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv1.getPickedEdgeState(), Color.black, Color.red));
+        vv1.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(vv1.getPickedVertexState(),
+                Color.red, Color.yellow));
+        // add default listeners for ToolTips
+        vv1.setVertexToolTipTransformer(new ToStringLabeller());
+        vv2.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        Container content = getContentPane();
+        JPanel panel = new JPanel(new GridLayout(1,0));
+        panel.add(new GraphZoomScrollPane(vv1));
+        panel.add(new GraphZoomScrollPane(vv2));
+
+        content.add(panel);
+        
+        // create a GraphMouse for each view
+        final DefaultModalGraphMouse<String, Number> gm1
+        	= new DefaultModalGraphMouse<String, Number>();
+
+        DefaultModalGraphMouse<String, Number> gm2
+        	= new DefaultModalGraphMouse<String, Number>();
+
+        vv1.setGraphMouse(gm1);
+        vv2.setGraphMouse(gm2);
+
+        // create zoom buttons for scaling the Function that is
+        // shared between the two models.
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv1, 1.1f, vv1.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv1, 1/1.1f, vv1.getCenter());
+            }
+        });
+        
+        JPanel zoomPanel = new JPanel(new GridLayout(1,2));
+        zoomPanel.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        
+        JPanel modePanel = new JPanel();
+        modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
+        gm1.getModeComboBox().addItemListener(gm2.getModeListener());
+        modePanel.add(gm1.getModeComboBox());
+
+        JPanel controls = new JPanel();
+        zoomPanel.add(plus);
+        zoomPanel.add(minus);
+        controls.add(zoomPanel);
+        controls.add(modePanel);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+
+    public static void main(String[] args) {
+        JFrame f = new JFrame();
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.getContentPane().add(new TwoModelDemo());
+        f.pack();
+        f.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/UnicodeLabelDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/UnicodeLabelDemo.java
new file mode 100644
index 0000000..7102ef1
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/UnicodeLabelDemo.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.graph.DirectedSparseGraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.decorators.VertexIconShapeTransformer;
+import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer;
+import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer;
+
+/**
+ * A demo that shows flag images as vertices, and uses unicode
+ * to render vertex labels.
+ * 
+ * @author Tom Nelson 
+ * 
+ */
+public class UnicodeLabelDemo {
+
+    /**
+     * the graph
+     */
+    Graph<Integer,Number> graph;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<Integer,Number> vv;
+    
+    boolean showLabels;
+    
+    public UnicodeLabelDemo() {
+        
+        // create a simple graph for the demo
+        graph = new DirectedSparseGraph<Integer,Number>();
+        Integer[] v = createVertices(10);
+        createEdges(v);
+        Map<Integer, Icon> iconMap = new HashMap<Integer, Icon>();
+        
+        vv =  new VisualizationViewer<Integer,Number>(new FRLayout<Integer,Number>(graph));
+        vv.getRenderContext().setVertexLabelTransformer(new UnicodeVertexStringer<Integer>(v));
+        vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.cyan));
+        vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.cyan));
+        VertexIconShapeTransformer<Integer> vertexIconShapeFunction =
+            new VertexIconShapeTransformer<Integer>(new EllipseVertexShapeTransformer<Integer>());
+        Function<Integer, Icon> vertexIconFunction = Functions.forMap(iconMap);
+        vv.getRenderContext().setVertexShapeTransformer(vertexIconShapeFunction);
+        vv.getRenderContext().setVertexIconTransformer(vertexIconFunction);
+        loadImages(v, iconMap);
+        vertexIconShapeFunction.setIconMap(iconMap);
+        vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<Integer>(vv.getPickedVertexState(), Color.white,  Color.yellow));
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv.getPickedEdgeState(), Color.black, Color.lightGray));
+
+        vv.setBackground(Color.white);
+
+        // add my listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        // create a frome to hold the graph
+        final JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        content.add(panel);
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        
+        final DefaultModalGraphMouse<Integer, Number> gm
+        	= new DefaultModalGraphMouse<Integer,Number>();
+        vv.setGraphMouse(gm);
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+
+        JCheckBox lo = new JCheckBox("Show Labels");
+        lo.addItemListener(new ItemListener(){
+            public void itemStateChanged(ItemEvent e) {
+                showLabels = e.getStateChange() == ItemEvent.SELECTED;
+                vv.repaint();
+            }
+        });
+        lo.setSelected(true);
+        
+        JPanel controls = new JPanel();
+        controls.add(plus);
+        controls.add(minus);
+        controls.add(lo);
+        controls.add(gm.getModeComboBox());
+        content.add(controls, BorderLayout.SOUTH);
+
+        frame.pack();
+        frame.setVisible(true);
+    }
+    
+    
+    class UnicodeVertexStringer<V> implements Function<V,String> {
+
+        Map<V,String> map = new HashMap<V,String>();
+        Map<V,Icon> iconMap = new HashMap<V,Icon>();
+        String[] labels = {
+                "\u0057\u0065\u006C\u0063\u006F\u006D\u0065\u0020\u0074\u006F\u0020JUNG\u0021",               
+                "\u6B22\u8FCE\u4F7F\u7528\u0020\u0020JUNG\u0021",
+                "\u0414\u043E\u0431\u0440\u043E\u0020\u043F\u043E\u0436\u0430\u043B\u043E\u0432\u0430\u0422\u044A\u0020\u0432\u0020JUNG\u0021",
+                "\u0042\u0069\u0065\u006E\u0076\u0065\u006E\u0075\u0065\u0020\u0061\u0075\u0020JUNG\u0021",
+                "\u0057\u0069\u006C\u006B\u006F\u006D\u006D\u0065\u006E\u0020\u007A\u0075\u0020JUNG\u0021",
+                "JUNG\u3078\u3087\u3045\u3053\u305D\u0021",
+//                "\u0053\u00E9\u006A\u0061\u0020\u0042\u0065\u006D\u0076\u0069\u006E\u0064\u006F\u0020JUNG\u0021",
+               "\u0042\u0069\u0065\u006E\u0076\u0065\u006E\u0069\u0064\u0061\u0020\u0061\u0020JUNG\u0021"
+        };
+        
+        public UnicodeVertexStringer(V[] vertices) {
+            for(int i=0; i<vertices.length; i++) {
+                map.put(vertices[i], labels[i%labels.length]);
+            }
+        }
+        
+        /**
+         * @see edu.uci.ics.jung.graph.decorators.VertexStringer#getLabel(edu.uci.ics.jung.graph.Vertex)
+         */
+        public String getLabel(V v) {
+            if(showLabels) {
+                return (String)map.get(v);
+            } else {
+                return "";
+            }
+        }
+        
+		public String apply(V input) {
+			return getLabel(input);
+		}
+    }
+    
+    /**
+     * create some vertices
+     * @param count how many to create
+     * @return the Vertices in an array
+     */
+    private Integer[] createVertices(int count) {
+        Integer[] v = new Integer[count];
+        for (int i = 0; i < count; i++) {
+            v[i] = new Integer(i);
+            graph.addVertex(v[i]);
+        }
+        return v;
+    }
+
+    /**
+     * create edges for this demo graph
+     * @param v an array of Vertices to connect
+     */
+    void createEdges(Integer[] v) {
+        graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[3], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[4], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[4], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[3], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[1], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[1], v[4], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[8], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[3], v[8], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[6], v[7], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[7], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[9], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[9], v[8], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[7], v[6], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[6], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[4], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[5], v[4], EdgeType.DIRECTED);
+    }
+
+    protected void loadImages(Integer[] vertices, Map<Integer,Icon> imageMap) {
+        
+        ImageIcon[] icons = null;
+        try {
+            icons = new ImageIcon[] {
+                    new ImageIcon(getClass().getResource("/images/united-states.gif")),
+                    new ImageIcon(getClass().getResource("/images/china.gif")),
+                    new ImageIcon(getClass().getResource("/images/russia.gif")),
+                    new ImageIcon(getClass().getResource("/images/france.gif")),
+                    new ImageIcon(getClass().getResource("/images/germany.gif")),
+                    new ImageIcon(getClass().getResource("/images/japan.gif")),
+                    new ImageIcon(getClass().getResource("/images/spain.gif"))
+            };
+        } catch(Exception ex) {
+            System.err.println("You need flags.jar in your classpath to see the flag icons.");
+        }
+        for(int i=0; icons != null && i<vertices.length; i++) {
+            imageMap.put(vertices[i],icons[i%icons.length]);
+        }
+    }
+
+    public static void main(String[] args) 
+    {
+        new UnicodeLabelDemo();
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexCollapseDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexCollapseDemo.java
new file mode 100644
index 0000000..fbe8250
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexCollapseDemo.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.geom.Point2D;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationModel;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.subLayout.GraphCollapser;
+import edu.uci.ics.jung.visualization.util.PredicatedParallelEdgeIndexFunction;
+
+
+/**
+ * A demo that shows how collections of vertices can be collapsed
+ * into a single vertex. In this demo, the vertices that are
+ * collapsed are those mouse-picked by the user. Any criteria
+ * could be used to form the vertex collections to be collapsed,
+ * perhaps some common characteristic of those vertex objects.
+ * 
+ * Note that the collection types don't use generics in this
+ * demo, because the vertices are of two types: String for plain
+ * vertices, and {@code Graph<String,Number>} for the collapsed vertices.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings({"serial", "rawtypes", "unchecked"})
+public class VertexCollapseDemo extends JApplet {
+
+    String instructions =
+        "<html>Use the mouse to select multiple vertices"+
+        "<p>either by dragging a region, or by shift-clicking"+
+        "<p>on multiple vertices."+
+        "<p>After you select vertices, use the Collapse button"+
+        "<p>to combine them into a single vertex."+
+        "<p>Select a 'collapsed' vertex and use the Expand button"+
+        "<p>to restore the collapsed vertices."+
+        "<p>The Restore button will restore the original graph."+
+        "<p>If you select 2 (and only 2) vertices, then press"+
+        "<p>the Compress Edges button, parallel edges between"+
+        "<p>those two vertices will no longer be expanded."+
+        "<p>If you select 2 (and only 2) vertices, then press"+
+        "<p>the Expand Edges button, parallel edges between"+
+        "<p>those two vertices will be expanded."+
+        "<p>You can drag the vertices with the mouse." +
+        "<p>Use the 'Picking'/'Transforming' combo-box to switch"+
+        "<p>between picking and transforming mode.</html>";
+    /**
+     * the graph
+     */
+    Graph graph;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer vv;
+    
+    Layout layout;
+    
+    GraphCollapser collapser;
+
+    public VertexCollapseDemo() {
+        
+        // create a simple graph for the demo
+        graph = 
+            TestGraphs.getOneComponentGraph();
+        collapser = new GraphCollapser(graph);
+        
+        layout = new FRLayout(graph);
+
+        Dimension preferredSize = new Dimension(400,400);
+        final VisualizationModel visualizationModel = 
+            new DefaultVisualizationModel(layout, preferredSize);
+        vv =  new VisualizationViewer(visualizationModel, preferredSize);
+        
+        vv.getRenderContext().setVertexShapeTransformer(new ClusterVertexShapeFunction());
+        
+        final PredicatedParallelEdgeIndexFunction eif = PredicatedParallelEdgeIndexFunction.getInstance();
+        final Set exclusions = new HashSet();
+        eif.setPredicate(new Predicate() {
+
+			public boolean apply(Object e) {
+				
+				return exclusions.contains(e);
+			}});
+        
+        
+        vv.getRenderContext().setParallelEdgeIndexFunction(eif);
+
+        vv.setBackground(Color.white);
+        
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller() {
+
+			@Override
+			public String apply(Object v) {
+				if(v instanceof Graph) {
+					return ((Graph)v).getVertices().toString();
+				}
+				return super.apply(v);
+			}});
+        
+        /**
+         * the regular graph mouse for the normal view
+         */
+        final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse();
+
+        vv.setGraphMouse(graphMouse);
+        
+        Container content = getContentPane();
+        GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv);
+        content.add(gzsp);
+        
+        JComboBox modeBox = graphMouse.getModeComboBox();
+        modeBox.addItemListener(graphMouse.getModeListener());
+        graphMouse.setMode(ModalGraphMouse.Mode.PICKING);
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        JButton collapse = new JButton("Collapse");
+        collapse.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                Collection picked = new HashSet(vv.getPickedVertexState().getPicked());
+                if(picked.size() > 1) {
+                    Graph inGraph = layout.getGraph();
+                    Graph clusterGraph = collapser.getClusterGraph(inGraph, picked);
+
+                    Graph g = collapser.collapse(layout.getGraph(), clusterGraph);
+                    double sumx = 0;
+                    double sumy = 0;
+                    for(Object v : picked) {
+                    	Point2D p = (Point2D)layout.apply(v);
+                    	sumx += p.getX();
+                    	sumy += p.getY();
+                    }
+                    Point2D cp = new Point2D.Double(sumx/picked.size(), sumy/picked.size());
+                    vv.getRenderContext().getParallelEdgeIndexFunction().reset();
+                    layout.setGraph(g);
+                    layout.setLocation(clusterGraph, cp);
+                    vv.getPickedVertexState().clear();
+                    vv.repaint();
+                }
+            }});
+        
+        JButton compressEdges = new JButton("Compress Edges");
+        compressEdges.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				Collection picked = vv.getPickedVertexState().getPicked();
+				if(picked.size() == 2) {
+					Pair pair = new Pair(picked);
+					Graph graph = layout.getGraph();
+					Collection edges = new HashSet(graph.getIncidentEdges(pair.getFirst()));
+					edges.retainAll(graph.getIncidentEdges(pair.getSecond()));
+					exclusions.addAll(edges);
+					vv.repaint();
+				}
+				
+			}});
+        
+        JButton expandEdges = new JButton("Expand Edges");
+        expandEdges.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				Collection picked = vv.getPickedVertexState().getPicked();
+				if(picked.size() == 2) {
+					Pair pair = new Pair(picked);
+					Graph graph = layout.getGraph();
+					Collection edges = new HashSet(graph.getIncidentEdges(pair.getFirst()));
+					edges.retainAll(graph.getIncidentEdges(pair.getSecond()));
+					exclusions.removeAll(edges);
+					vv.repaint();
+				}
+				
+			}});
+        
+        JButton expand = new JButton("Expand");
+        expand.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                Collection picked = new HashSet(vv.getPickedVertexState().getPicked());
+                for(Object v : picked) {
+                    if(v instanceof Graph) {
+                        
+                        Graph g = collapser.expand(layout.getGraph(), (Graph)v);
+                        vv.getRenderContext().getParallelEdgeIndexFunction().reset();
+                        layout.setGraph(g);
+                    }
+                    vv.getPickedVertexState().clear();
+                   vv.repaint();
+                }
+            }});
+        
+        JButton reset = new JButton("Reset");
+        reset.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                layout.setGraph(graph);
+                exclusions.clear();
+                vv.repaint();
+            }});
+        
+        JButton help = new JButton("Help");
+        help.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                JOptionPane.showMessageDialog((JComponent)e.getSource(), instructions, "Help", JOptionPane.PLAIN_MESSAGE);
+            }
+        });
+
+        JPanel controls = new JPanel();
+        JPanel zoomControls = new JPanel(new GridLayout(2,1));
+        zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        zoomControls.add(plus);
+        zoomControls.add(minus);
+        controls.add(zoomControls);
+        JPanel collapseControls = new JPanel(new GridLayout(3,1));
+        collapseControls.setBorder(BorderFactory.createTitledBorder("Picked"));
+        collapseControls.add(collapse);
+        collapseControls.add(expand);
+        collapseControls.add(compressEdges);
+        collapseControls.add(expandEdges);
+        collapseControls.add(reset);
+        controls.add(collapseControls);
+        controls.add(modeBox);
+        controls.add(help);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+    
+    /**
+     * a demo class that will create a vertex shape that is either a
+     * polygon or star. The number of sides corresponds to the number
+     * of vertices that were collapsed into the vertex represented by
+     * this shape.
+     * 
+     * @author Tom Nelson
+     *
+     * @param <V> the vertex type
+     */
+    class ClusterVertexShapeFunction<V> extends EllipseVertexShapeTransformer<V> {
+
+        ClusterVertexShapeFunction() {
+            setSizeTransformer(new ClusterVertexSizeFunction<V>(20));
+        }
+        @Override
+        public Shape apply(V v) {
+            if(v instanceof Graph) {
+                int size = ((Graph)v).getVertexCount();
+                if (size < 8) {   
+                    int sides = Math.max(size, 3);
+                    return factory.getRegularPolygon(v, sides);
+                }
+                else {
+                    return factory.getRegularStar(v, size);
+                }
+            }
+            return super.apply(v);
+        }
+    }
+    
+    /**
+     * A demo class that will make vertices larger if they represent
+     * a collapsed collection of original vertices
+     * @author Tom Nelson
+     *
+     * @param <V> the vertex type
+     */
+    class ClusterVertexSizeFunction<V> implements Function<V,Integer> {
+    	int size;
+        public ClusterVertexSizeFunction(Integer size) {
+            this.size = size;
+        }
+
+        public Integer apply(V v) {
+            if(v instanceof Graph) {
+                return 30;
+            }
+            return size;
+        }
+    }
+
+    public static void main(String[] args) {
+        JFrame f = new JFrame();
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.getContentPane().add(new VertexCollapseDemo());
+        f.pack();
+        f.setVisible(true);
+    }
+}
+
+
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexCollapseDemoWithLayouts.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexCollapseDemoWithLayouts.java
new file mode 100644
index 0000000..e942ad7
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexCollapseDemoWithLayouts.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.geom.Point2D;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+
+import edu.uci.ics.jung.algorithms.layout.CircleLayout;
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.ISOMLayout;
+import edu.uci.ics.jung.algorithms.layout.KKLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.SpringLayout;
+import edu.uci.ics.jung.algorithms.layout.SpringLayout2;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationModel;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.layout.LayoutTransition;
+import edu.uci.ics.jung.visualization.subLayout.GraphCollapser;
+import edu.uci.ics.jung.visualization.util.Animator;
+import edu.uci.ics.jung.visualization.util.PredicatedParallelEdgeIndexFunction;
+
+
+/**
+ * A demo that shows how collections of vertices can be collapsed
+ * into a single vertex. In this demo, the vertices that are
+ * collapsed are those mouse-picked by the user. Any criteria
+ * could be used to form the vertex collections to be collapsed,
+ * perhaps some common characteristic of those vertex objects.
+ * 
+ * Note that the collection types don't use generics in this
+ * demo, because the vertices are of two types: String for plain
+ * vertices, and {@code Graph<String, Number>} for the collapsed vertices.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class VertexCollapseDemoWithLayouts extends JApplet {
+
+    String instructions =
+        "<html>Use the mouse to select multiple vertices"+
+        "<p>either by dragging a region, or by shift-clicking"+
+        "<p>on multiple vertices."+
+        "<p>After you select vertices, use the Collapse button"+
+        "<p>to combine them into a single vertex."+
+        "<p>Select a 'collapsed' vertex and use the Expand button"+
+        "<p>to restore the collapsed vertices."+
+        "<p>The Restore button will restore the original graph."+
+        "<p>If you select 2 (and only 2) vertices, then press"+
+        "<p>the Compress Edges button, parallel edges between"+
+        "<p>those two vertices will no longer be expanded."+
+        "<p>If you select 2 (and only 2) vertices, then press"+
+        "<p>the Expand Edges button, parallel edges between"+
+        "<p>those two vertices will be expanded."+
+        "<p>You can drag the vertices with the mouse." +
+        "<p>Use the 'Picking'/'Transforming' combo-box to switch"+
+        "<p>between picking and transforming mode.</html>";
+    /**
+     * the graph
+     */
+    @SuppressWarnings("rawtypes")
+	Graph graph;
+    @SuppressWarnings("rawtypes")
+	Graph collapsedGraph;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    @SuppressWarnings("rawtypes")
+	VisualizationViewer vv;
+    
+    @SuppressWarnings("rawtypes")
+	Layout layout;
+    
+    GraphCollapser collapser;
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+	public VertexCollapseDemoWithLayouts() {
+        
+        // create a simple graph for the demo
+        graph = 
+            TestGraphs.getOneComponentGraph();
+        collapsedGraph = graph;
+        collapser = new GraphCollapser(graph);
+        
+        layout = new FRLayout(graph);
+
+        Dimension preferredSize = new Dimension(400,400);
+        final VisualizationModel visualizationModel = 
+            new DefaultVisualizationModel(layout, preferredSize);
+        vv =  new VisualizationViewer(visualizationModel, preferredSize);
+        
+        vv.getRenderContext().setVertexShapeTransformer(new ClusterVertexShapeFunction());
+        
+        final PredicatedParallelEdgeIndexFunction eif = PredicatedParallelEdgeIndexFunction.getInstance();
+        final Set exclusions = new HashSet();
+        eif.setPredicate(new Predicate() {
+
+			public boolean apply(Object e) {
+				
+				return exclusions.contains(e);
+			}});
+        
+        
+        vv.getRenderContext().setParallelEdgeIndexFunction(eif);
+
+        vv.setBackground(Color.white);
+        
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller() {
+
+			/* (non-Javadoc)
+			 * @see edu.uci.ics.jung.visualization.decorators.DefaultToolTipFunction#getToolTipText(java.lang.Object)
+			 */
+			@Override
+			public String apply(Object v) {
+				if(v instanceof Graph) {
+					return ((Graph)v).getVertices().toString();
+				}
+				return super.apply(v);
+			}});
+        
+        /**
+         * the regular graph mouse for the normal view
+         */
+        final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse();
+
+        vv.setGraphMouse(graphMouse);
+        
+        Container content = getContentPane();
+        GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv);
+        content.add(gzsp);
+        
+        JComboBox modeBox = graphMouse.getModeComboBox();
+        modeBox.addItemListener(graphMouse.getModeListener());
+        graphMouse.setMode(ModalGraphMouse.Mode.PICKING);
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        JButton collapse = new JButton("Collapse");
+        collapse.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                Collection picked = new HashSet(vv.getPickedVertexState().getPicked());
+                if(picked.size() > 1) {
+                    Graph inGraph = layout.getGraph();
+                    Graph clusterGraph = collapser.getClusterGraph(inGraph, picked);
+
+                    Graph g = collapser.collapse(layout.getGraph(), clusterGraph);
+                    collapsedGraph = g;
+                    double sumx = 0;
+                    double sumy = 0;
+                    for(Object v : picked) {
+                    	Point2D p = (Point2D)layout.apply(v);
+                    	sumx += p.getX();
+                    	sumy += p.getY();
+                    }
+                    Point2D cp = new Point2D.Double(sumx/picked.size(), sumy/picked.size());
+                    vv.getRenderContext().getParallelEdgeIndexFunction().reset();
+                    layout.setGraph(g);
+                    layout.setLocation(clusterGraph, cp);
+                    vv.getPickedVertexState().clear();
+                    vv.repaint();
+                }
+            }});
+        
+        JButton compressEdges = new JButton("Compress Edges");
+        compressEdges.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				Collection picked = vv.getPickedVertexState().getPicked();
+				if(picked.size() == 2) {
+					Pair pair = new Pair(picked);
+					Graph graph = layout.getGraph();
+					Collection edges = new HashSet(graph.getIncidentEdges(pair.getFirst()));
+					edges.retainAll(graph.getIncidentEdges(pair.getSecond()));
+					exclusions.addAll(edges);
+					vv.repaint();
+				}
+				
+			}});
+        
+        JButton expandEdges = new JButton("Expand Edges");
+        expandEdges.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				Collection picked = vv.getPickedVertexState().getPicked();
+				if(picked.size() == 2) {
+					Pair pair = new Pair(picked);
+					Graph graph = layout.getGraph();
+					Collection edges = new HashSet(graph.getIncidentEdges(pair.getFirst()));
+					edges.retainAll(graph.getIncidentEdges(pair.getSecond()));
+					exclusions.removeAll(edges);
+					vv.repaint();
+				}
+				
+			}});
+        
+        JButton expand = new JButton("Expand");
+        expand.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                Collection picked = new HashSet(vv.getPickedVertexState().getPicked());
+                for(Object v : picked) {
+                    if(v instanceof Graph) {
+                        
+                        Graph g = collapser.expand(layout.getGraph(), (Graph)v);
+                        vv.getRenderContext().getParallelEdgeIndexFunction().reset();
+                        layout.setGraph(g);
+                    }
+                    vv.getPickedVertexState().clear();
+                   vv.repaint();
+                }
+            }});
+        
+        JButton reset = new JButton("Reset");
+        reset.addActionListener(new ActionListener() {
+
+            public void actionPerformed(ActionEvent e) {
+                layout.setGraph(graph);
+                exclusions.clear();
+                vv.repaint();
+            }});
+        
+        JButton help = new JButton("Help");
+        help.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                JOptionPane.showMessageDialog((JComponent)e.getSource(), instructions, "Help", JOptionPane.PLAIN_MESSAGE);
+            }
+        });
+        Class[] combos = getCombos();
+        final JComboBox jcb = new JComboBox(combos);
+        // use a renderer to shorten the layout name presentation
+        jcb.setRenderer(new DefaultListCellRenderer() {
+            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+                String valueString = value.toString();
+                valueString = valueString.substring(valueString.lastIndexOf('.')+1);
+                return super.getListCellRendererComponent(list, valueString, index, isSelected,
+                        cellHasFocus);
+            }
+        });
+        jcb.addActionListener(new LayoutChooser(jcb, vv));
+        jcb.setSelectedItem(FRLayout.class);
+
+
+        JPanel controls = new JPanel();
+        JPanel zoomControls = new JPanel(new GridLayout(2,1));
+        zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        zoomControls.add(plus);
+        zoomControls.add(minus);
+        controls.add(zoomControls);
+        JPanel collapseControls = new JPanel(new GridLayout(3,1));
+        collapseControls.setBorder(BorderFactory.createTitledBorder("Picked"));
+        collapseControls.add(collapse);
+        collapseControls.add(expand);
+        collapseControls.add(compressEdges);
+        collapseControls.add(expandEdges);
+        collapseControls.add(reset);
+        controls.add(collapseControls);
+        controls.add(modeBox);
+        controls.add(help);
+        controls.add(jcb);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+    
+    /**
+     * a demo class that will create a vertex shape that is either a
+     * polygon or star. The number of sides corresponds to the number
+     * of vertices that were collapsed into the vertex represented by
+     * this shape.
+     * 
+     * @author Tom Nelson
+     *
+     * @param <V> the vertex type
+     */
+    class ClusterVertexShapeFunction<V> extends EllipseVertexShapeTransformer<V> {
+
+        ClusterVertexShapeFunction() {
+            setSizeTransformer(new ClusterVertexSizeFunction<V>(20));
+        }
+        @Override
+        public Shape apply(V v) {
+            if(v instanceof Graph) {
+                @SuppressWarnings("rawtypes")
+				int size = ((Graph)v).getVertexCount();
+                if (size < 8) {   
+                    int sides = Math.max(size, 3);
+                    return factory.getRegularPolygon(v, sides);
+                }
+                else {
+                    return factory.getRegularStar(v, size);
+                }
+            }
+            return super.apply(v);
+        }
+    }
+    
+    /**
+     * A demo class that will make vertices larger if they represent
+     * a collapsed collection of original vertices
+     * @author Tom Nelson
+     *
+     * @param <V> the vertex type
+     */
+    class ClusterVertexSizeFunction<V> implements Function<V,Integer> {
+    	int size;
+        public ClusterVertexSizeFunction(Integer size) {
+            this.size = size;
+        }
+
+        public Integer apply(V v) {
+            if(v instanceof Graph) {
+                return 30;
+            }
+            return size;
+        }
+    }
+
+	private class LayoutChooser implements ActionListener
+    {
+        private final JComboBox<?> jcb;
+        @SuppressWarnings("rawtypes")
+		private final VisualizationViewer vv;
+
+        private LayoutChooser(JComboBox<?> jcb, VisualizationViewer<Object, ?> vv)
+        {
+            super();
+            this.jcb = jcb;
+            this.vv = vv;
+        }
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        public void actionPerformed(ActionEvent arg0)
+        {
+            Object[] constructorArgs =
+                { collapsedGraph };
+
+			Class<? extends Layout> layoutC = 
+                (Class<? extends Layout>) jcb.getSelectedItem();
+            try
+            {
+                Constructor<? extends Layout> constructor = layoutC
+                        .getConstructor(new Class[] {Graph.class});
+                Object o = constructor.newInstance(constructorArgs);
+                Layout l = (Layout) o;
+                l.setInitializer(vv.getGraphLayout());
+                l.setSize(vv.getSize());
+                layout = l;
+				LayoutTransition lt =
+					new LayoutTransition(vv, vv.getGraphLayout(), l);
+				Animator animator = new Animator(lt);
+				animator.start();
+				vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
+				vv.repaint();
+                
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+    }
+    /**
+     * @return
+     */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private Class<? extends Layout>[] getCombos()
+    {
+        List<Class<? extends Layout>> layouts = new ArrayList<Class<? extends Layout>>();
+        layouts.add(KKLayout.class);
+        layouts.add(FRLayout.class);
+        layouts.add(CircleLayout.class);
+        layouts.add(SpringLayout.class);
+        layouts.add(SpringLayout2.class);
+        layouts.add(ISOMLayout.class);
+        return layouts.toArray(new Class[0]);
+    }
+
+    public static void main(String[] args) {
+        JFrame f = new JFrame();
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.getContentPane().add(new VertexCollapseDemoWithLayouts());
+        f.pack();
+        f.setVisible(true);
+    }
+}
+
+
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexImageShaperDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexImageShaperDemo.java
new file mode 100644
index 0000000..b1285b5
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexImageShaperDemo.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.GridLayout;
+import java.awt.Image;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer;
+import edu.uci.ics.jung.graph.DirectedSparseGraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.LayeredIcon;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.decorators.VertexIconShapeTransformer;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.renderers.BasicVertexRenderer;
+import edu.uci.ics.jung.visualization.renderers.Checkmark;
+import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer;
+import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer;
+import edu.uci.ics.jung.visualization.util.ImageShapeUtils;
+
+/**
+ * Demonstrates the use of images to represent graph vertices.
+ * The images are supplied via the VertexShapeFunction so that
+ * both the image and its shape can be utilized.
+ * 
+ * The images used in this demo (courtesy of slashdot.org) are
+ * rectangular but with a transparent background. When vertices
+ * are represented by these images, it looks better if the actual
+ * shape of the opaque part of the image is computed so that the
+ * edge arrowheads follow the visual shape of the image. This demo
+ * uses the FourPassImageShaper class to compute the Shape from
+ * an image with transparent background.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+public class VertexImageShaperDemo extends JApplet {
+
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = -4332663871914930864L;
+	
+	private static final int VERTEX_COUNT=11;
+
+	/**
+     * the graph
+     */
+    DirectedSparseGraph<Number, Number> graph;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<Number, Number> vv;
+    
+    /**
+     * some icon names to use
+     */
+    String[] iconNames = {
+            "apple",
+            "os",
+            "x",
+            "linux",
+            "inputdevices",
+            "wireless",
+            "graphics3",
+            "gamespcgames",
+            "humor",
+            "music",
+            "privacy"
+    };
+    
+    public VertexImageShaperDemo() {
+        
+        // create a simple graph for the demo
+        graph = new DirectedSparseGraph<Number,Number>();
+        createGraph(VERTEX_COUNT);
+        
+        // a Map for the labels
+        Map<Number,String> map = new HashMap<Number,String>();
+        for(int i=0; i<VERTEX_COUNT; i++) {
+            map.put(i, iconNames[i%iconNames.length]);
+        }
+        
+        // a Map for the Icons
+        Map<Number,Icon> iconMap = new HashMap<Number,Icon>();
+        for(int i=0; i<VERTEX_COUNT; i++) {
+            String name = "/images/topic"+iconNames[i]+".gif";
+            try {
+                Icon icon = 
+                    new LayeredIcon(new ImageIcon(VertexImageShaperDemo.class.getResource(name)).getImage());
+                iconMap.put(i, icon);
+            } catch(Exception ex) {
+                System.err.println("You need slashdoticons.jar in your classpath to see the image "+name);
+            }
+        }
+        
+        FRLayout<Number, Number> layout = new FRLayout<Number, Number>(graph);
+        layout.setMaxIterations(100);
+        layout.setInitializer(new RandomLocationTransformer<Number>(new Dimension(400,400), 0));
+        vv =  new VisualizationViewer<Number, Number>(layout, new Dimension(400,400));
+        
+        // This demo uses a special renderer to turn outlines on and off.
+        // you do not need to do this in a real application.
+        // Instead, just let vv use the Renderer it already has
+        vv.getRenderer().setVertexRenderer(new DemoRenderer<Number,Number>());
+
+        Function<Number,Paint> vpf = 
+            new PickableVertexPaintTransformer<Number>(vv.getPickedVertexState(), Color.white, Color.yellow);
+        vv.getRenderContext().setVertexFillPaintTransformer(vpf);
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv.getPickedEdgeState(), Color.black, Color.cyan));
+
+        vv.setBackground(Color.white);
+        
+        
+        final Function<Number,String> vertexStringerImpl = 
+            new VertexStringerImpl<Number,String>(map);
+        vv.getRenderContext().setVertexLabelTransformer(vertexStringerImpl);
+        vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.cyan));
+        vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.cyan));
+//        vv.getRenderContext().setEdgeLabelTransformer(new Function<Number,String>() {
+//        	URL url = getClass().getResource("/images/lightning-s.gif");
+//			public String transform(Number input) {
+//				
+//				return "<html><img src="+url+" height=10 width=21>"+input.toString();
+//			}});
+        
+        // For this demo only, I use a special class that lets me turn various
+        // features on and off. For a real application, use VertexIconShapeTransformer instead.
+        final DemoVertexIconShapeTransformer<Number> vertexIconShapeTransformer =
+            new DemoVertexIconShapeTransformer<Number>(new EllipseVertexShapeTransformer<Number>());
+        vertexIconShapeTransformer.setIconMap(iconMap);
+
+        final DemoVertexIconTransformer<Number> vertexIconTransformer
+        	= new DemoVertexIconTransformer<Number>(iconMap);
+        
+        vv.getRenderContext().setVertexShapeTransformer(vertexIconShapeTransformer);
+        vv.getRenderContext().setVertexIconTransformer(vertexIconTransformer);
+        
+        // un-comment for RStar Tree visual testing
+        //vv.addPostRenderPaintable(new BoundingRectanglePaintable(vv.getRenderContext(), vv.getGraphLayout()));
+
+        // Get the pickedState and add a listener that will decorate the
+        // Vertex images with a checkmark icon when they are picked
+        PickedState<Number> ps = vv.getPickedVertexState();
+        ps.addItemListener(new PickWithIconListener<Number>(vertexIconTransformer));
+        
+        vv.addPostRenderPaintable(new VisualizationViewer.Paintable(){
+            int x;
+            int y;
+            Font font;
+            FontMetrics metrics;
+            int swidth;
+            int sheight;
+            String str = "Thank You, slashdot.org, for the images!";
+            
+            public void paint(Graphics g) {
+                Dimension d = vv.getSize();
+                if(font == null) {
+                    font = new Font(g.getFont().getName(), Font.BOLD, 20);
+                    metrics = g.getFontMetrics(font);
+                    swidth = metrics.stringWidth(str);
+                    sheight = metrics.getMaxAscent()+metrics.getMaxDescent();
+                    x = (d.width-swidth)/2;
+                    y = (int)(d.height-sheight*1.5);
+                }
+                g.setFont(font);
+                Color oldColor = g.getColor();
+                g.setColor(Color.lightGray);
+                g.drawString(str, x, y);
+                g.setColor(oldColor);
+            }
+            public boolean useTransform() {
+                return false;
+            }
+        });
+
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        Container content = getContentPane();
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        content.add(panel);
+        
+        final DefaultModalGraphMouse<Number,Number> graphMouse = new DefaultModalGraphMouse<Number,Number>();
+        vv.setGraphMouse(graphMouse);
+        vv.addKeyListener(graphMouse.getModeKeyListener());
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+
+        JCheckBox shape = new JCheckBox("Shape");
+        shape.addItemListener(new ItemListener(){
+
+            public void itemStateChanged(ItemEvent e) {
+                vertexIconShapeTransformer.setShapeImages(e.getStateChange()==ItemEvent.SELECTED);
+                vv.repaint();
+            }
+        });
+        shape.setSelected(true);
+
+        JCheckBox fill = new JCheckBox("Fill");
+        fill.addItemListener(new ItemListener(){
+
+            public void itemStateChanged(ItemEvent e) {
+                vertexIconTransformer.setFillImages(e.getStateChange()==ItemEvent.SELECTED);
+                vv.repaint();
+            }
+        });
+        fill.setSelected(true);
+        
+        JCheckBox drawOutlines = new JCheckBox("Outline");
+        drawOutlines.addItemListener(new ItemListener(){
+
+            public void itemStateChanged(ItemEvent e) {
+                vertexIconTransformer.setOutlineImages(e.getStateChange()==ItemEvent.SELECTED);
+                vv.repaint();
+            }
+        });
+        
+        JComboBox<?> modeBox = graphMouse.getModeComboBox();
+        JPanel modePanel = new JPanel();
+        modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
+        modePanel.add(modeBox);
+        
+        JPanel scaleGrid = new JPanel(new GridLayout(1,0));
+        scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        JPanel labelFeatures = new JPanel(new GridLayout(1,0));
+        labelFeatures.setBorder(BorderFactory.createTitledBorder("Image Effects"));
+        JPanel controls = new JPanel();
+        scaleGrid.add(plus);
+        scaleGrid.add(minus);
+        controls.add(scaleGrid);
+        labelFeatures.add(shape);
+        labelFeatures.add(fill);
+        labelFeatures.add(drawOutlines);
+
+        controls.add(labelFeatures);
+        controls.add(modePanel);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+    
+    /**
+     * When Vertices are picked, add a checkmark icon to the imager.
+     * Remove the icon when a Vertex is unpicked
+     * @author Tom Nelson
+     *
+     */
+    public static class PickWithIconListener<V> implements ItemListener {
+        Function<V, Icon> imager;
+        Icon checked;
+        
+        public PickWithIconListener(Function<V, Icon> imager) {
+            this.imager = imager;
+            checked = new Checkmark();
+        }
+
+        public void itemStateChanged(ItemEvent e) {
+            @SuppressWarnings("unchecked")
+			Icon icon = imager.apply((V)e.getItem());
+            if(icon != null && icon instanceof LayeredIcon) {
+                if(e.getStateChange() == ItemEvent.SELECTED) {
+                    ((LayeredIcon)icon).add(checked);
+                } else {
+                    ((LayeredIcon)icon).remove(checked);
+                }
+            }
+        }
+    }
+    /**
+     * A simple implementation of VertexStringer that
+     * gets Vertex labels from a Map  
+     * 
+     * @author Tom Nelson
+     *
+     *
+     */
+    public static class VertexStringerImpl<V,S> implements Function<V,String> {
+        
+        Map<V,String> map = new HashMap<V,String>();
+        
+        boolean enabled = true;
+        
+        public VertexStringerImpl(Map<V,String> map) {
+            this.map = map;
+        }
+        
+        /* (non-Javadoc)
+         * @see edu.uci.ics.jung.graph.decorators.VertexStringer#getLabel(edu.uci.ics.jung.graph.Vertex)
+         */
+        public String apply(V v) {
+            if(isEnabled()) {
+                return map.get(v);
+            } else {
+                return "";
+            }
+        }
+        
+        /**
+         * @return Returns the enabled.
+         */
+        public boolean isEnabled() {
+            return enabled;
+        }
+        
+        /**
+         * @param enabled The enabled to set.
+         */
+        public void setEnabled(boolean enabled) {
+            this.enabled = enabled;
+        }
+    }
+    
+    /**
+     * create some vertices
+     * @param count how many to create
+     * @return the Vertices in an array
+     */
+    private void createGraph(int vertexCount) {
+        for (int i = 0; i < vertexCount; i++) {
+            graph.addVertex(i);
+        }
+    	int j=0;
+        graph.addEdge(j++, 0, 1, EdgeType.DIRECTED);
+        graph.addEdge(j++, 3, 0, EdgeType.DIRECTED);
+        graph.addEdge(j++, 0, 4, EdgeType.DIRECTED);
+        graph.addEdge(j++, 4, 5, EdgeType.DIRECTED);
+        graph.addEdge(j++, 5, 3, EdgeType.DIRECTED);
+        graph.addEdge(j++, 2, 1, EdgeType.DIRECTED);
+        graph.addEdge(j++, 4, 1, EdgeType.DIRECTED);
+        graph.addEdge(j++, 8, 2, EdgeType.DIRECTED);
+        graph.addEdge(j++, 3, 8, EdgeType.DIRECTED);
+        graph.addEdge(j++, 6, 7, EdgeType.DIRECTED);
+        graph.addEdge(j++, 7, 5, EdgeType.DIRECTED);
+        graph.addEdge(j++, 0, 9, EdgeType.DIRECTED);
+        graph.addEdge(j++, 9, 8, EdgeType.DIRECTED);
+        graph.addEdge(j++, 7, 6, EdgeType.DIRECTED);
+        graph.addEdge(j++, 6, 5, EdgeType.DIRECTED);
+        graph.addEdge(j++, 4, 2, EdgeType.DIRECTED);
+        graph.addEdge(j++, 5, 4, EdgeType.DIRECTED);
+        graph.addEdge(j++, 4, 10, EdgeType.DIRECTED);
+        graph.addEdge(j++, 10, 4, EdgeType.DIRECTED);
+    }
+
+    /** 
+     * This class exists only to provide settings to turn on/off shapes and image fill
+     * in this demo.
+     * 
+     * <p>For a real application, just use {@code Functions.forMap(iconMap)} to provide a
+     * {@code Function<V, Icon>}.
+     */
+    public static class DemoVertexIconTransformer<V> implements Function<V,Icon> {
+        boolean fillImages = true;
+        boolean outlineImages = false;
+        Map<V, Icon> iconMap = new HashMap<V, Icon>();
+        
+        public DemoVertexIconTransformer(Map<V, Icon> iconMap) {
+        	this.iconMap = iconMap;
+        }
+
+        /**
+         * @return Returns the fillImages.
+         */
+        public boolean isFillImages() {
+            return fillImages;
+        }
+        /**
+         * @param fillImages The fillImages to set.
+         */
+        public void setFillImages(boolean fillImages) {
+            this.fillImages = fillImages;
+        }
+
+        public boolean isOutlineImages() {
+            return outlineImages;
+        }
+        public void setOutlineImages(boolean outlineImages) {
+            this.outlineImages = outlineImages;
+        }
+        
+        public Icon apply(V v) {
+            if(fillImages) {
+                return (Icon)iconMap.get(v);
+            } else {
+                return null;
+            }
+        }
+    }
+    
+    /** 
+     * this class exists only to provide settings to turn on/off shapes and image fill
+     * in this demo.
+     * In a real application, use VertexIconShapeTransformer instead.
+     * 
+     */
+    public static class DemoVertexIconShapeTransformer<V> extends VertexIconShapeTransformer<V> {
+        
+        boolean shapeImages = true;
+
+        public DemoVertexIconShapeTransformer(Function<V,Shape> delegate) {
+            super(delegate);
+        }
+
+        /**
+         * @return Returns the shapeImages.
+         */
+        public boolean isShapeImages() {
+            return shapeImages;
+        }
+        /**
+         * @param shapeImages The shapeImages to set.
+         */
+        public void setShapeImages(boolean shapeImages) {
+            shapeMap.clear();
+            this.shapeImages = shapeImages;
+        }
+
+        public Shape transform(V v) {
+			Icon icon = (Icon) iconMap.get(v);
+
+			if (icon != null && icon instanceof ImageIcon) {
+
+				Image image = ((ImageIcon) icon).getImage();
+
+				Shape shape = shapeMap.get(image);
+				if (shape == null) {
+					if (shapeImages) {
+						shape = ImageShapeUtils.getShape(image, 30);
+					} else {
+						shape = new Rectangle2D.Float(0, 0, 
+								image.getWidth(null), image.getHeight(null));
+					}
+                    if(shape.getBounds().getWidth() > 0 && 
+                            shape.getBounds().getHeight() > 0) {
+                        int width = image.getWidth(null);
+                        int height = image.getHeight(null);
+                        AffineTransform transform = 
+                            AffineTransform.getTranslateInstance(-width / 2, -height / 2);
+                        shape = transform.createTransformedShape(shape);
+                        shapeMap.put(image, shape);
+                    }
+				}
+				return shape;
+			} else {
+				return delegate.apply(v);
+			}
+		}
+    }
+    
+    /**
+     * a special renderer that can turn outlines on and off
+     * in this demo.
+     * You won't need this for a real application.
+     * Use BasicVertexRenderer instead
+     * 
+     * @author Tom Nelson
+     *
+     */
+    class DemoRenderer<V,E> extends BasicVertexRenderer<V,E> {
+//        public void paintIconForVertex(RenderContext<V,E> rc, V v, Layout<V,E> layout) {
+//        	
+//            Point2D p = layout.transform(v);
+//            p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p);
+//            float x = (float)p.getX();
+//            float y = (float)p.getY();
+//
+//            GraphicsDecorator g = rc.getGraphicsContext();
+//            boolean outlineImages = false;
+//            Function<V,Icon> vertexIconFunction = rc.getVertexIconTransformer();
+//            
+//            if(vertexIconFunction instanceof DemoVertexIconTransformer) {
+//                outlineImages = ((DemoVertexIconTransformer<V>)vertexIconFunction).isOutlineImages();
+//            }
+//            Icon icon = vertexIconFunction.transform(v);
+//            if(icon == null || outlineImages) {
+//                
+//                Shape s = AffineTransform.getTranslateInstance(x,y).
+//                    createTransformedShape(rc.getVertexShapeTransformer().transform(v));
+//                paintShapeForVertex(rc, v, s);
+//            }
+//            if(icon != null) {
+//                int xLoc = (int) (x - icon.getIconWidth()/2);
+//                int yLoc = (int) (y - icon.getIconHeight()/2);
+//                icon.paintIcon(rc.getScreenDevice(), g.getDelegate(), xLoc, yLoc);
+//            }
+//        }
+    }
+    
+    public static void main(String[] args) {
+        JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+        content.add(new VertexImageShaperDemo());
+        frame.pack();
+        frame.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexLabelAsShapeDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexLabelAsShapeDemo.java
new file mode 100644
index 0000000..d12f2f5
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexLabelAsShapeDemo.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.Paint;
+import java.awt.Stroke;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationModel;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer;
+import edu.uci.ics.jung.visualization.renderers.GradientVertexRenderer;
+import edu.uci.ics.jung.visualization.renderers.VertexLabelAsShapeRenderer;
+
+
+/**
+ * This demo shows how to use the vertex labels themselves as 
+ * the vertex shapes. Additionally, it shows html labels
+ * so they are multi-line, and gradient painting of the
+ * vertex labels.
+ * 
+ * @author Tom Nelson
+ * 
+ */
+public class VertexLabelAsShapeDemo extends JApplet {
+
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = 1017336668368978842L;
+
+    Graph<String,Number> graph;
+
+    VisualizationViewer<String,Number> vv;
+    
+    Layout<String,Number> layout;
+    
+    /**
+     * create an instance of a simple graph with basic controls
+     */
+    public VertexLabelAsShapeDemo() {
+        
+        // create a simple graph for the demo
+        graph = TestGraphs.getOneComponentGraph();
+        
+        layout = new FRLayout<String,Number>(graph);
+
+        Dimension preferredSize = new Dimension(400,400);
+        final VisualizationModel<String,Number> visualizationModel = 
+            new DefaultVisualizationModel<String,Number>(layout, preferredSize);
+        vv =  new VisualizationViewer<String,Number>(visualizationModel, preferredSize);
+        
+        // this class will provide both label drawing and vertex shapes
+        VertexLabelAsShapeRenderer<String,Number> vlasr = new VertexLabelAsShapeRenderer<String,Number>(vv.getRenderContext());
+        
+        // customize the render context
+        vv.getRenderContext().setVertexLabelTransformer(
+        		// this chains together Functions so that the html tags
+        		// are prepended to the toString method output
+        		Functions.<Object,String,String>compose(
+        				new Function<String,String>(){
+							public String apply(String input) {
+								return "<html><center>Vertex<p>"+input;
+							}}, new ToStringLabeller()));
+        vv.getRenderContext().setVertexShapeTransformer(vlasr);
+        vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.red));
+        vv.getRenderContext().setEdgeDrawPaintTransformer(Functions.<Paint>constant(Color.yellow));
+        vv.getRenderContext().setEdgeStrokeTransformer(Functions.<Stroke>constant(new BasicStroke(2.5f)));
+        
+        // customize the renderer
+        vv.getRenderer().setVertexRenderer(new GradientVertexRenderer<String,Number>(Color.gray, Color.white, true));
+        vv.getRenderer().setVertexLabelRenderer(vlasr);
+
+        vv.setBackground(Color.black);
+        
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        final DefaultModalGraphMouse<String,Number> graphMouse = 
+            new DefaultModalGraphMouse<String,Number>();
+
+        vv.setGraphMouse(graphMouse);
+        vv.addKeyListener(graphMouse.getModeKeyListener());
+        
+        Container content = getContentPane();
+        GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv);
+        content.add(gzsp);
+        
+        JComboBox<?> modeBox = graphMouse.getModeComboBox();
+        modeBox.addItemListener(graphMouse.getModeListener());
+        graphMouse.setMode(ModalGraphMouse.Mode.TRANSFORMING);
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        
+        JPanel controls = new JPanel();
+        JPanel zoomControls = new JPanel(new GridLayout(2,1));
+        zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        zoomControls.add(plus);
+        zoomControls.add(minus);
+        controls.add(zoomControls);
+        controls.add(modeBox);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+    
+    public static void main(String[] args) {
+        JFrame f = new JFrame();
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.getContentPane().add(new VertexLabelAsShapeDemo());
+        f.pack();
+        f.setVisible(true);
+    }
+}
+
+
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexLabelPositionDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexLabelPositionDemo.java
new file mode 100644
index 0000000..9444615
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/VertexLabelPositionDemo.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JMenuBar;
+import javax.swing.JPanel;
+
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.TestGraphs;
+import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.VisualizationModel;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;
+
+/**
+ * Demonstrates vertex label positioning 
+ * controlled by the user.
+ * In the AUTO setting, labels are placed according to
+ * which quadrant the vertex is in
+ * 
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class VertexLabelPositionDemo extends JApplet {
+
+    /**
+     * the graph
+     */
+    Graph<String,Number> graph;
+    
+    FRLayout<String,Number> graphLayout;
+    
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<String,Number> vv;
+    
+    ScalingControl scaler;
+    
+    /**
+     * create an instance of a simple graph with controls to
+     * demo the zoomand hyperbolic features.
+     * 
+     */
+    public VertexLabelPositionDemo() {
+        
+        // create a simple graph for the demo
+        graph = TestGraphs.getOneComponentGraph();
+        
+        graphLayout = new FRLayout<String,Number>(graph);
+        graphLayout.setMaxIterations(1000);
+
+        Dimension preferredSize = new Dimension(600,600);
+        
+        final VisualizationModel<String,Number> visualizationModel = 
+            new DefaultVisualizationModel<String,Number>(graphLayout, preferredSize);
+        vv =  new VisualizationViewer<String,Number>(visualizationModel, preferredSize);
+
+        PickedState<String> ps = vv.getPickedVertexState();
+        PickedState<Number> pes = vv.getPickedEdgeState();
+        vv.getRenderContext().setVertexFillPaintTransformer(new PickableVertexPaintTransformer<String>(ps, Color.red, Color.yellow));
+        vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(pes, Color.black, Color.cyan));
+        vv.setBackground(Color.white);
+        vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.W);
+        
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        
+        // add a listener for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        
+        Container content = getContentPane();
+        GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv);
+        content.add(gzsp);
+        
+        /**
+         * the regular graph mouse for the normal view
+         */
+        final DefaultModalGraphMouse<String,Number> graphMouse
+        	= new DefaultModalGraphMouse<String,Number>();
+
+        vv.setGraphMouse(graphMouse);
+        vv.addKeyListener(graphMouse.getModeKeyListener());
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+        JPanel positionPanel = new JPanel();
+        positionPanel.setBorder(BorderFactory.createTitledBorder("Label Position"));
+        JMenuBar menubar = new JMenuBar();
+        menubar.add(graphMouse.getModeMenu());
+        gzsp.setCorner(menubar);
+        JComboBox<Position> cb = new JComboBox<Position>();
+        cb.addItem(Renderer.VertexLabel.Position.N);
+        cb.addItem(Renderer.VertexLabel.Position.NE);
+        cb.addItem(Renderer.VertexLabel.Position.E);
+        cb.addItem(Renderer.VertexLabel.Position.SE);
+        cb.addItem(Renderer.VertexLabel.Position.S);
+        cb.addItem(Renderer.VertexLabel.Position.SW);
+        cb.addItem(Renderer.VertexLabel.Position.W);
+        cb.addItem(Renderer.VertexLabel.Position.NW);
+        cb.addItem(Renderer.VertexLabel.Position.N);
+        cb.addItem(Renderer.VertexLabel.Position.CNTR);
+        cb.addItem(Renderer.VertexLabel.Position.AUTO);
+        cb.addItemListener(new ItemListener() {
+			public void itemStateChanged(ItemEvent e) {
+				Renderer.VertexLabel.Position position = 
+					(Renderer.VertexLabel.Position)e.getItem();
+				vv.getRenderer().getVertexLabelRenderer().setPosition(position);
+				vv.repaint();
+			}});
+        cb.setSelectedItem(Renderer.VertexLabel.Position.SE);
+        positionPanel.add(cb);
+        JPanel controls = new JPanel();
+        JPanel zoomControls = new JPanel(new GridLayout(2,1));
+        zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom"));
+        zoomControls.add(plus);
+        zoomControls.add(minus);
+        
+        controls.add(zoomControls);
+        controls.add(positionPanel);
+        content.add(controls, BorderLayout.SOUTH);
+    }
+
+    public static void main(String[] args) {
+        JFrame f = new JFrame();
+        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        f.getContentPane().add(new VertexLabelPositionDemo());
+        f.pack();
+        f.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/VisualizationImageServerDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/VisualizationImageServerDemo.java
new file mode 100644
index 0000000..0e0b5fa
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/VisualizationImageServerDemo.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Image;
+import java.awt.Paint;
+import java.awt.geom.Point2D;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.layout.KKLayout;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.VisualizationImageServer;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.renderers.GradientVertexRenderer;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+import edu.uci.ics.jung.visualization.renderers.BasicVertexLabelRenderer.InsidePositioner;
+
+
+/**
+ * Demonstrates VisualizationImageServer.
+ * @author Tom Nelson
+ * 
+ */
+public class VisualizationImageServerDemo {
+
+    /**
+     * the graph
+     */
+    DirectedSparseMultigraph<String, Number> graph;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationImageServer<String, Number> vv;
+    
+    /**
+     * 
+     */
+    public VisualizationImageServerDemo() {
+        
+        // create a simple graph for the demo
+        graph = new DirectedSparseMultigraph<String, Number>();
+        String[] v = createVertices(10);
+        createEdges(v);
+
+        vv =  new VisualizationImageServer<String,Number>(new KKLayout<String,Number>(graph), new Dimension(600,600));
+
+        vv.getRenderer().setVertexRenderer(
+        		new GradientVertexRenderer<String,Number>(
+        				Color.white, Color.red, 
+        				Color.white, Color.blue,
+        				vv.getPickedVertexState(),
+        				false));
+        vv.getRenderContext().setEdgeDrawPaintTransformer(Functions.<Paint>constant(Color.lightGray));
+        vv.getRenderContext().setArrowFillPaintTransformer(Functions.<Paint>constant(Color.lightGray));
+        vv.getRenderContext().setArrowDrawPaintTransformer(Functions.<Paint>constant(Color.lightGray));
+        
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        vv.getRenderer().getVertexLabelRenderer().setPositioner(new InsidePositioner());
+        vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.AUTO);
+
+        // create a frome to hold the graph
+        final JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        
+        Image im = vv.getImage(new Point2D.Double(300,300), new Dimension(600,600));
+        Icon icon = new ImageIcon(im);
+        JLabel label = new JLabel(icon);
+        content.add(label);
+        frame.pack();
+        frame.setVisible(true);
+    }
+    
+    /**
+     * create some vertices
+     * @param count how many to create
+     * @return the Vertices in an array
+     */
+    private String[] createVertices(int count) {
+        String[] v = new String[count];
+        for (int i = 0; i < count; i++) {
+        	v[i] = "V"+i;
+            graph.addVertex(v[i]);
+        }
+        return v;
+    }
+
+    /**
+     * create edges for this demo graph
+     * @param v an array of Vertices to connect
+     */
+    void createEdges(String[] v) {
+        graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[3], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[4], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[4], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[3], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[1], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[1], v[4], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[8], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[3], v[8], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[6], v[7], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[7], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[0], v[9], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[9], v[8], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[7], v[6], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[6], v[5], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[4], v[2], EdgeType.DIRECTED);
+        graph.addEdge(new Double(Math.random()), v[5], v[4], EdgeType.DIRECTED);
+    }
+
+    public static void main(String[] args) 
+    {
+        new VisualizationImageServerDemo();
+        
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/WorldMapGraphDemo.java b/jung-samples/src/main/java/edu/uci/ics/jung/samples/WorldMapGraphDemo.java
new file mode 100644
index 0000000..285321f
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/WorldMapGraphDemo.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.samples;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.ImageIcon;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.StaticLayout;
+import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.AbstractModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
+import edu.uci.ics.jung.visualization.renderers.GradientVertexRenderer;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+import edu.uci.ics.jung.visualization.renderers.BasicVertexLabelRenderer.InsidePositioner;
+
+
+/**
+ * Shows a graph overlaid on a world map image.
+ * Scaling of the graph also scales the image background.
+ * @author Tom Nelson
+ * 
+ */
+ at SuppressWarnings("serial")
+public class WorldMapGraphDemo extends JApplet {
+
+    /**
+     * the graph
+     */
+    Graph<String, Number> graph;
+
+    /**
+     * the visual component and renderer for the graph
+     */
+    VisualizationViewer<String, Number> vv;
+    
+	Map<String,String[]> map = new HashMap<String,String[]>();
+   	List<String> cityList;
+
+
+    
+    /**
+     * create an instance of a simple graph with controls to
+     * demo the zoom features.
+     * 
+     */
+    public WorldMapGraphDemo() {
+        setLayout(new BorderLayout());
+        
+		map.put("TYO", new String[] {"35 40 N", "139 45 E"});
+   		map.put("PEK", new String[] {"39 55 N", "116 26 E"});
+   		map.put("MOW", new String[] {"55 45 N", "37 42 E"});
+   		map.put("JRS", new String[] {"31 47 N", "35 13 E"});
+   		map.put("CAI", new String[] {"30 03 N", "31 15 E"});
+   		map.put("CPT", new String[] {"33 55 S", "18 22 E"});
+   		map.put("PAR", new String[] {"48 52 N", "2 20 E"});
+   		map.put("LHR", new String[] {"51 30 N", "0 10 W"});
+   		map.put("HNL", new String[] {"21 18 N", "157 51 W"});
+   		map.put("NYC", new String[] {"40 77 N", "73 98 W"});
+   		map.put("SFO", new String[] {"37 62 N", "122 38 W"});
+   		map.put("AKL", new String[] {"36 55 S", "174 47 E"});
+   		map.put("BNE", new String[] {"27 28 S", "153 02 E"});
+   		map.put("HKG", new String[] {"22 15 N", "114 10 E"});
+   		map.put("KTM", new String[] {"27 42 N", "85 19 E"});
+   		map.put("IST", new String[] {"41 01 N", "28 58 E"});
+   		map.put("STO", new String[] {"59 20 N", "18 03 E"});
+   		map.put("RIO", new String[] {"22 54 S", "43 14 W"});
+   		map.put("LIM", new String[] {"12 03 S", "77 03 W"});
+   		map.put("YTO", new String[] {"43 39 N", "79 23 W"});
+
+   		cityList = new ArrayList<String>(map.keySet());
+
+        // create a simple graph for the demo
+        graph = new DirectedSparseMultigraph<String, Number>();
+        createVertices();
+        createEdges();
+        
+        ImageIcon mapIcon = null;
+        String imageLocation = "/images/political_world_map.jpg";
+        try {
+            mapIcon = 
+            	    new ImageIcon(getClass().getResource(imageLocation));
+        } catch(Exception ex) {
+            System.err.println("Can't load \""+imageLocation+"\"");
+        }
+        final ImageIcon icon = mapIcon;
+
+        Dimension layoutSize = new Dimension(2000,1000);
+        
+        Layout<String,Number> layout = new StaticLayout<String,Number>(graph,
+        		Functions.<String,String[],Point2D>compose(
+        				new LatLonPixelTransformer(new Dimension(2000,1000)),
+        				new CityTransformer(map))
+        		);
+//        		new ChainedTransformer(new Function[]{
+//        				new CityTransformer(map),
+//        				new LatLonPixelTransformer(new Dimension(2000,1000))
+//        		}));
+        	
+        layout.setSize(layoutSize);
+        vv =  new VisualizationViewer<String,Number>(layout,
+        		new Dimension(800,400));
+        
+        if(icon != null) {
+            vv.addPreRenderPaintable(new VisualizationViewer.Paintable(){
+                public void paint(Graphics g) {
+                	Graphics2D g2d = (Graphics2D)g;
+                	AffineTransform oldXform = g2d.getTransform();
+                    AffineTransform lat = 
+                    	vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getTransform();
+                    AffineTransform vat = 
+                    	vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getTransform();
+                    AffineTransform at = new AffineTransform();
+                    at.concatenate(g2d.getTransform());
+                    at.concatenate(vat);
+                    at.concatenate(lat);
+                    g2d.setTransform(at);
+                    g.drawImage(icon.getImage(), 0, 0,
+                    		icon.getIconWidth(),icon.getIconHeight(),vv);
+                    g2d.setTransform(oldXform);
+                }
+                public boolean useTransform() { return false; }
+            });
+        }
+
+        vv.getRenderer().setVertexRenderer(
+        		new GradientVertexRenderer<String,Number>(
+        				Color.white, Color.red, 
+        				Color.white, Color.blue,
+        				vv.getPickedVertexState(),
+        				false));
+        
+        // add my listeners for ToolTips
+        vv.setVertexToolTipTransformer(new ToStringLabeller());
+        vv.setEdgeToolTipTransformer(new Function<Number,String>() {
+			public String apply(Number edge) {
+				return "E"+graph.getEndpoints(edge).toString();
+			}});
+        
+        vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller());
+        vv.getRenderer().getVertexLabelRenderer().setPositioner(new InsidePositioner());
+        vv.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.AUTO);
+        
+        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
+        add(panel);
+        final AbstractModalGraphMouse graphMouse = new DefaultModalGraphMouse<String, Number>();
+        vv.setGraphMouse(graphMouse);
+        
+        vv.addKeyListener(graphMouse.getModeKeyListener());
+        vv.setToolTipText("<html><center>Type 'p' for Pick mode<p>Type 't' for Transform mode");
+        
+        final ScalingControl scaler = new CrossoverScalingControl();
+
+        JButton plus = new JButton("+");
+        plus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1.1f, vv.getCenter());
+            }
+        });
+        JButton minus = new JButton("-");
+        minus.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                scaler.scale(vv, 1/1.1f, vv.getCenter());
+            }
+        });
+
+        JButton reset = new JButton("reset");
+        reset.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).setToIdentity();
+				vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).setToIdentity();
+			}});
+
+        JPanel controls = new JPanel();
+        controls.add(plus);
+        controls.add(minus);
+        controls.add(reset);
+        add(controls, BorderLayout.SOUTH);
+    }
+    
+    /**
+     * create some vertices
+     * @param count how many to create
+     * @return the Vertices in an array
+     */
+    private void createVertices() {
+        for (String city : map.keySet()) {
+            graph.addVertex(city);
+        }
+    }
+
+    /**
+     * create edges for this demo graph
+     * @param v an array of Vertices to connect
+     */
+    void createEdges() {
+     	
+    	for(int i=0; i<map.keySet().size()*1.3; i++) {
+    		graph.addEdge(new Double(Math.random()), randomCity(), randomCity(), EdgeType.DIRECTED);
+    	}
+    }
+    
+    private String randomCity() {
+    	int m = cityList.size();
+    	return cityList.get((int)(Math.random()*m));
+    }
+    
+    static class CityTransformer implements Function<String,String[]> {
+
+    	Map<String,String[]> map;
+    	public CityTransformer(Map<String,String[]> map) {
+    		this.map = map;
+    	}
+
+    	/**
+    	 * transform airport code to latlon string
+    	 */
+		public String[] apply(String city) {
+			return map.get(city);
+		}
+    }
+    
+    static class LatLonPixelTransformer implements Function<String[],Point2D> {
+    	Dimension d;
+    	int startOffset;
+    	
+    	public LatLonPixelTransformer(Dimension d) {
+    		this.d = d;
+    	}
+    	/**
+    	 * transform a lat
+    	 */
+		public Point2D apply(String[] latlon) {
+			double latitude = 0;
+			double longitude = 0;
+			String[] lat = latlon[0].split(" ");
+			String[] lon = latlon[1].split(" ");
+			latitude = Integer.parseInt(lat[0]) + Integer.parseInt(lat[1])/60f;
+			latitude *= d.height/180f;
+			longitude = Integer.parseInt(lon[0]) + Integer.parseInt(lon[1])/60f;
+			longitude *= d.width/360f;
+			if(lat[2].equals("N")) {
+				latitude = d.height / 2 - latitude;
+				
+			} else { // assume S
+				latitude = d.height / 2 + latitude;
+			}
+			
+			if(lon[2].equals("W")) {
+				longitude = d.width / 2 - longitude;
+				
+			} else { // assume E
+				longitude = d.width / 2 + longitude;
+			}
+			
+			return new Point2D.Double(longitude,latitude);
+		}
+    	
+    }
+
+    public static void main(String[] args) {
+        // create a frome to hold the graph
+        final JFrame frame = new JFrame();
+        Container content = frame.getContentPane();
+        content.add(new WorldMapGraphDemo());
+        frame.pack();
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        frame.setVisible(true);
+    }
+}
diff --git a/jung-samples/src/main/java/edu/uci/ics/jung/samples/package.html b/jung-samples/src/main/java/edu/uci/ics/jung/samples/package.html
new file mode 100644
index 0000000..870f6b1
--- /dev/null
+++ b/jung-samples/src/main/java/edu/uci/ics/jung/samples/package.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Sample applications created using JUNG, largely focused on visualization.  
+Current features demonstrated in the samples include:
+<ul>
+<li>visualization of changing graphs
+<li>visualization animation
+<li>layouts: BalloonLayout, TreeLayout, RadialTreeLayout 
+<li>clustering, shortest path, minimum spanning tree
+<li>sublayouts
+<li>label variations: orientation, position
+<li>customization of color, shape, stroke
+<li>on-the-fly visualization filtering
+<li>graph editor
+<li>vertex 'collapsing'
+<li>multiple models, multiple views with the same model
+<li>vertices as Icons
+<li>magnifying lens overlay
+<li>satellite view
+</ul>
+
+</body>
+</html>
diff --git a/jung-samples/src/main/resources/datasets/simple.net b/jung-samples/src/main/resources/datasets/simple.net
new file mode 100644
index 0000000..72aef49
--- /dev/null
+++ b/jung-samples/src/main/resources/datasets/simple.net
@@ -0,0 +1,33 @@
+*Vertices 16
+1 "in1"
+2 "in2"
+3 "in3"
+4 "in4"
+5 "in5"
+6 "in6"
+7 "in7"
+8 "in8"
+9 "out1"
+10 "out2"
+11 "out3"
+12 "out4"
+13 "out5"
+14 "out6"
+15 "out7"
+16 "out8"
+*Edgeslist
+1 2 5 8 9 16
+2 3 6 9 10
+3 4 7 10 11
+4 5 8 11 12
+5 6 12 13
+6 7 13 14
+7 8 14 15
+8 15 16
+9 10 16
+10 11
+11 12
+12 13
+13 14
+14 15
+15 16
diff --git a/jung-samples/src/main/resources/datasets/smyth.net b/jung-samples/src/main/resources/datasets/smyth.net
new file mode 100644
index 0000000..3dd2973
--- /dev/null
+++ b/jung-samples/src/main/resources/datasets/smyth.net
@@ -0,0 +1,842 @@
+*Vertices     286
+       1 "M Pazzani"                              0.0000    0.0000    0.5000
+       2 "M Fernandez"                            0.0000    0.0000    0.5000
+       3 "O Mangasarian"                          0.0000    0.0000    0.5000
+       4 "M Hearst"                               0.0000    0.0000    0.5000
+       5 "M Jordan"                               0.0000    0.0000    0.5000
+       6 "C Lai"                                  0.0000    0.0000    0.5000
+       7 "T Richardson"                           0.0000    0.0000    0.5000
+       8 "C Meek"                                 0.0000    0.0000    0.5000
+       9 "C Glymour"                              0.0000    0.0000    0.5000
+      10 "P Sabes"                                0.0000    0.0000    0.5000
+      11 "J Frank"                                0.0000    0.0000    0.5000
+      12 "P Cheeseman"                            0.0000    0.0000    0.5000
+      13 "J Stutz"                                0.0000    0.0000    0.5000
+      14 "M Wellman"                              0.0000    0.0000    0.5000
+      15 "D Cohn"                                 0.0000    0.0000    0.5000
+      16 "A Smith"                                0.0000    0.0000    0.5000
+      17 "B Draper"                               0.0000    0.0000    0.5000
+      18 "J Erickson"                             0.0000    0.0000    0.5000
+      19 "C Brodley"                              0.0000    0.0000    0.5000
+      20 "P Smyth"                                0.0000    0.0000    0.5000
+      21 "J Weber"                                0.0000    0.0000    0.5000
+      22 "J Malik"                                0.0000    0.0000    0.5000
+      23 "D Heckerman"                            0.0000    0.0000    0.5000
+      24 "A Jones"                                0.0000    0.0000    0.5000
+      25 "E Knill"                                0.0000    0.0000    0.5000
+      26 "B Bollobas"                             0.0000    0.0000    0.5000
+      27 "G Das"                                  0.0000    0.0000    0.5000
+      28 "D Gunopulos"                            0.0000    0.0000    0.5000
+      29 "H Mannila"                              0.0000    0.0000    0.5000
+      30 "S Teng"                                 0.0000    0.0000    0.5000
+      31 "D Mitchell"                             0.0000    0.0000    0.5000
+      32 "R Khardon"                              0.0000    0.0000    0.5000
+      33 "D Roth"                                 0.0000    0.0000    0.5000
+      34 "A Raftery"                              0.0000    0.0000    0.5000
+      35 "D Madigan"                              0.0000    0.0000    0.5000
+      36 "J Hoeting"                              0.0000    0.0000    0.5000
+      37 "M Kersten"                              0.0000    0.0000    0.5000
+      38 "O Miglino"                              0.0000    0.0000    0.5000
+      39 "K Nafasi"                               0.0000    0.0000    0.5000
+      40 "C Taylor"                               0.0000    0.0000    0.5000
+      41 "D Wolpert"                              0.0000    0.0000    0.5000
+      42 "W Macready"                             0.0000    0.0000    0.5000
+      43 "A Hill"                                 0.0000    0.0000    0.5000
+      44 "T Cootes"                               0.0000    0.0000    0.5000
+      45 "Z Ghahramani"                           0.0000    0.0000    0.5000
+      46 "S Dumais"                               0.0000    0.0000    0.5000
+      47 "P Chan"                                 0.0000    0.0000    0.5000
+      48 "S Soatto"                               0.0000    0.0000    0.5000
+      49 "P Perona"                               0.0000    0.0000    0.5000
+      50 "R Frezza"                               0.0000    0.0000    0.5000
+      51 "G Picci"                                0.0000    0.0000    0.5000
+      52 "M Mitchell"                             0.0000    0.0000    0.5000
+      53 "T Jaakkola"                             0.0000    0.0000    0.5000
+      54 "D Haussler"                             0.0000    0.0000    0.5000
+      55 "P Orponen"                              0.0000    0.0000    0.5000
+      56 "K Murphy"                               0.0000    0.0000    0.5000
+      57 "H Greenspan"                            0.0000    0.0000    0.5000
+      58 "S Belongie"                             0.0000    0.0000    0.5000
+      59 "R Goodman"                              0.0000    0.0000    0.5000
+      60 "M Holsheimer"                           0.0000    0.0000    0.5000
+      61 "J Matousek"                             0.0000    0.0000    0.5000
+      62 "L Xu"                                   0.0000    0.0000    0.5000
+      63 "M Paterson"                             0.0000    0.0000    0.5000
+      64 "M Klemettinen"                          0.0000    0.0000    0.5000
+      65 "H Toivonen"                             0.0000    0.0000    0.5000
+      66 "A Weigend"                              0.0000    0.0000    0.5000
+      67 "S Jung"                                 0.0000    0.0000    0.5000
+      68 "J Boulicaut"                            0.0000    0.0000    0.5000
+      69 "S Casadei"                              0.0000    0.0000    0.5000
+      70 "S Mitter"                               0.0000    0.0000    0.5000
+      71 "J Adams"                                0.0000    0.0000    0.5000
+      72 "J Kosecka"                              0.0000    0.0000    0.5000
+      73 "R Paul"                                 0.0000    0.0000    0.5000
+      74 "A Farquhar"                             0.0000    0.0000    0.5000
+      75 "R Kohavi"                               0.0000    0.0000    0.5000
+      76 "M Jaeger"                               0.0000    0.0000    0.5000
+      77 "J Ostrowski"                            0.0000    0.0000    0.5000
+      78 "K Bennett"                              0.0000    0.0000    0.5000
+      79 "A Ng"                                   0.0000    0.0000    0.5000
+      80 "P Ronkainen"                            0.0000    0.0000    0.5000
+      81 "L Guibas"                               0.0000    0.0000    0.5000
+      82 "B Dom"                                  0.0000    0.0000    0.5000
+      83 "M Sahami"                               0.0000    0.0000    0.5000
+      84 "J Bouguet"                              0.0000    0.0000    0.5000
+      85 "J Rice"                                 0.0000    0.0000    0.5000
+      86 "D Dobkin"                               0.0000    0.0000    0.5000
+      87 "D Eppstein"                             0.0000    0.0000    0.5000
+      88 "T Hastie"                               0.0000    0.0000    0.5000
+      89 "P Agarwal"                              0.0000    0.0000    0.5000
+      90 "E Ukkonen"                              0.0000    0.0000    0.5000
+      91 "G Overton"                              0.0000    0.0000    0.5000
+      92 "J Aaronson"                             0.0000    0.0000    0.5000
+      93 "J Haas"                                 0.0000    0.0000    0.5000
+      94 "Z Wang"                                 0.0000    0.0000    0.5000
+      95 "D Hart"                                 0.0000    0.0000    0.5000
+      96 "G Gottlob"                              0.0000    0.0000    0.5000
+      97 "P Stolorz"                              0.0000    0.0000    0.5000
+      98 "F Yao"                                  0.0000    0.0000    0.5000
+      99 "A Robertson"                            0.0000    0.0000    0.5000
+     100 "E Horvitz"                              0.0000    0.0000    0.5000
+     101 "S Singh"                                0.0000    0.0000    0.5000
+     102 "B Fischer"                              0.0000    0.0000    0.5000
+     103 "T Leung"                                0.0000    0.0000    0.5000
+     104 "H Ahonen"                               0.0000    0.0000    0.5000
+     105 "O Heinonen"                             0.0000    0.0000    0.5000
+     106 "P Kilpelainen"                          0.0000    0.0000    0.5000
+     107 "M Atkinson"                             0.0000    0.0000    0.5000
+     108 "D Peel"                                 0.0000    0.0000    0.5000
+     109 "G Miller"                               0.0000    0.0000    0.5000
+     110 "S Russell"                              0.0000    0.0000    0.5000
+     111 "W Buntine"                              0.0000    0.0000    0.5000
+     112 "J Fortes"                               0.0000    0.0000    0.5000
+     113 "L Saul"                                 0.0000    0.0000    0.5000
+     114 "M Dillencourt"                          0.0000    0.0000    0.5000
+     115 "Y Weiss"                                0.0000    0.0000    0.5000
+     116 "C Volinsky"                             0.0000    0.0000    0.5000
+     117 "R Kronmal"                              0.0000    0.0000    0.5000
+     118 "A Gray"                                 0.0000    0.0000    0.5000
+     119 "S Hanks"                                0.0000    0.0000    0.5000
+     120 "V Tsaoussidis"                          0.0000    0.0000    0.5000
+     121 "H Badr"                                 0.0000    0.0000    0.5000
+     122 "D Kriegman"                             0.0000    0.0000    0.5000
+     123 "T Lane"                                 0.0000    0.0000    0.5000
+     124 "J Gilbert"                              0.0000    0.0000    0.5000
+     125 "P Debevec"                              0.0000    0.0000    0.5000
+     126 "J Kivinen"                              0.0000    0.0000    0.5000
+     127 "J Crowcroft"                            0.0000    0.0000    0.5000
+     128 "R Beigel"                               0.0000    0.0000    0.5000
+     129 "A Mayer"                                0.0000    0.0000    0.5000
+     130 "B MacLennan"                            0.0000    0.0000    0.5000
+     131 "G Edwards"                              0.0000    0.0000    0.5000
+     132 "C Matheus"                              0.0000    0.0000    0.5000
+     133 "G Piatetsky-Shapiro"                    0.0000    0.0000    0.5000
+     134 "D McNeill"                              0.0000    0.0000    0.5000
+     135 "P Utgoff"                               0.0000    0.0000    0.5000
+     136 "R Fikes"                                0.0000    0.0000    0.5000
+     137 "R Jacobs"                               0.0000    0.0000    0.5000
+     138 "M Weber"                                0.0000    0.0000    0.5000
+     139 "J Gavrin"                               0.0000    0.0000    0.5000
+     140 "K Chang"                                0.0000    0.0000    0.5000
+     141 "D Hirschberg"                           0.0000    0.0000    0.5000
+     142 "E Hunt"                                 0.0000    0.0000    0.5000
+     143 "S MacDonell"                            0.0000    0.0000    0.5000
+     144 "Z Galil"                                0.0000    0.0000    0.5000
+     145 "E Weydert"                              0.0000    0.0000    0.5000
+     146 "D Thomas"                               0.0000    0.0000    0.5000
+     147 "M Henzinger"                            0.0000    0.0000    0.5000
+     148 "M Burl"                                 0.0000    0.0000    0.5000
+     149 "M Bern"                                 0.0000    0.0000    0.5000
+     150 "P Sozou"                                0.0000    0.0000    0.5000
+     151 "J Graham"                               0.0000    0.0000    0.5000
+     152 "E Demaine"                              0.0000    0.0000    0.5000
+     153 "M Demaine"                              0.0000    0.0000    0.5000
+     154 "S Spence"                               0.0000    0.0000    0.5000
+     155 "D Geiger"                               0.0000    0.0000    0.5000
+     156 "M Friedl"                               0.0000    0.0000    0.5000
+     157 "T Eiter"                                0.0000    0.0000    0.5000
+     158 "E Alpaydin"                             0.0000    0.0000    0.5000
+     159 "S Solloway"                             0.0000    0.0000    0.5000
+     160 "C Hutchinson"                           0.0000    0.0000    0.5000
+     161 "J Waterton"                             0.0000    0.0000    0.5000
+     162 "D Cooper"                               0.0000    0.0000    0.5000
+     163 "A Barto"                                0.0000    0.0000    0.5000
+     164 "E Nikunen"                              0.0000    0.0000    0.5000
+     165 "E Dimauro"                              0.0000    0.0000    0.5000
+     166 "G Linden"                               0.0000    0.0000    0.5000
+     167 "A Verkamo"                              0.0000    0.0000    0.5000
+     168 "I Verkamo"                              0.0000    0.0000    0.5000
+     169 "L Goncalves"                            0.0000    0.0000    0.5000
+     170 "G Cooper"                               0.0000    0.0000    0.5000
+     171 "A Newton"                               0.0000    0.0000    0.5000
+     172 "R Kraft"                                0.0000    0.0000    0.5000
+     173 "J Breese"                               0.0000    0.0000    0.5000
+     174 "N Amenta"                               0.0000    0.0000    0.5000
+     175 "I Cadez"                                0.0000    0.0000    0.5000
+     176 "C McLaren"                              0.0000    0.0000    0.5000
+     177 "G McLachlan"                            0.0000    0.0000    0.5000
+     178 "G Barequet"                             0.0000    0.0000    0.5000
+     179 "M Meila"                                0.0000    0.0000    0.5000
+     180 "Q Morris"                               0.0000    0.0000    0.5000
+     181 "Y Song"                                 0.0000    0.0000    0.5000
+     182 "S Andersson"                            0.0000    0.0000    0.5000
+     183 "M Perlman"                              0.0000    0.0000    0.5000
+     184 "P Bradley"                              0.0000    0.0000    0.5000
+     185 "U Fayyad"                               0.0000    0.0000    0.5000
+     186 "D Wolf"                                 0.0000    0.0000    0.5000
+     187 "G Italiano"                             0.0000    0.0000    0.5000
+     188 "K Tumer"                                0.0000    0.0000    0.5000
+     189 "E Keogh"                                0.0000    0.0000    0.5000
+     190 "E Bernardo"                             0.0000    0.0000    0.5000
+     191 "E Ursella"                              0.0000    0.0000    0.5000
+     192 "C Triggs"                               0.0000    0.0000    0.5000
+     193 "D Sornette"                             0.0000    0.0000    0.5000
+     194 "E Friedman"                             0.0000    0.0000    0.5000
+     195 "L Daynes"                               0.0000    0.0000    0.5000
+     196 "T Printezis"                            0.0000    0.0000    0.5000
+     197 "T Pressburger"                          0.0000    0.0000    0.5000
+     198 "B Tuttle"                               0.0000    0.0000    0.5000
+     199 "J Haslam"                               0.0000    0.0000    0.5000
+     200 "G Page"                                 0.0000    0.0000    0.5000
+     201 "C Jackson"                              0.0000    0.0000    0.5000
+     202 "C Bishop"                               0.0000    0.0000    0.5000
+     203 "D Pavlov"                               0.0000    0.0000    0.5000
+     204 "M Dickerson"                            0.0000    0.0000    0.5000
+     205 "N de Freitas"                           0.0000    0.0000    0.5000
+     206 "C Stauss"                               0.0000    0.0000    0.5000
+     207 "R Kilgour"                              0.0000    0.0000    0.5000
+     208 "R Manduchi"                             0.0000    0.0000    0.5000
+     209 "G Graefe"                               0.0000    0.0000    0.5000
+     210 "R Shachter"                             0.0000    0.0000    0.5000
+     211 "K Mosurski"                             0.0000    0.0000    0.5000
+     212 "R Almond"                               0.0000    0.0000    0.5000
+     213 "D Young"                                0.0000    0.0000    0.5000
+     214 "A Lapedes"                              0.0000    0.0000    0.5000
+     215 "R Blasi"                                0.0000    0.0000    0.5000
+     216 "D Chickering"                           0.0000    0.0000    0.5000
+     217 "R Giancarlo"                            0.0000    0.0000    0.5000
+     218 "K Wheeler"                              0.0000    0.0000    0.5000
+     219 "A Lanitis"                              0.0000    0.0000    0.5000
+     220 "P Prado"                                0.0000    0.0000    0.5000
+     221 "X Ge"                                   0.0000    0.0000    0.5000
+     222 "S Payandeh"                             0.0000    0.0000    0.5000
+     223 "M Ghil"                                 0.0000    0.0000    0.5000
+     224 "K Ide"                                  0.0000    0.0000    0.5000
+     225 "H King"                                 0.0000    0.0000    0.5000
+     226 "I Dryden"                               0.0000    0.0000    0.5000
+     227 "B Thiesson"                             0.0000    0.0000    0.5000
+     228 "D Pregibon"                             0.0000    0.0000    0.5000
+     229 "A Korhola"                              0.0000    0.0000    0.5000
+     230 "H Olander"                              0.0000    0.0000    0.5000
+     231 "W Pratt"                                0.0000    0.0000    0.5000
+     232 "R Engelmore"                            0.0000    0.0000    0.5000
+     233 "A Brett"                                0.0000    0.0000    0.5000
+     234 "C Sayers"                               0.0000    0.0000    0.5000
+     235 "J Mao"                                  0.0000    0.0000    0.5000
+     236 "M Salmenkivi"                           0.0000    0.0000    0.5000
+     237 "A Mamdani"                              0.0000    0.0000    0.5000
+     238 "J Iv"                                   0.0000    0.0000    0.5000
+     239 "M Welling"                              0.0000    0.0000    0.5000
+     240 "B Kanefsky"                             0.0000    0.0000    0.5000
+     241 "W Taylor"                               0.0000    0.0000    0.5000
+     242 "C Strauss"                              0.0000    0.0000    0.5000
+     243 "K Walker"                               0.0000    0.0000    0.5000
+     244 "M Faghihi"                              0.0000    0.0000    0.5000
+     245 "E Di Bernardo"                          0.0000    0.0000    0.5000
+     246 "P Kube"                                 0.0000    0.0000    0.5000
+     247 "K Rommelse"                             0.0000    0.0000    0.5000
+     248 "D Rumelhart"                            0.0000    0.0000    0.5000
+     249 "S Gaffney"                              0.0000    0.0000    0.5000
+     250 "C Reina"                                0.0000    0.0000    0.5000
+     251 "S Chaudhuiri"                           0.0000    0.0000    0.5000
+     252 "P Sallis"                               0.0000    0.0000    0.5000
+     253 "K Laakso"                               0.0000    0.0000    0.5000
+     254 "B Levidow"                              0.0000    0.0000    0.5000
+     255 "D Donnell"                              0.0000    0.0000    0.5000
+     256 "M Tartagni"                             0.0000    0.0000    0.5000
+     257 "M Vanter"                               0.0000    0.0000    0.5000
+     258 "N Lawrence"                             0.0000    0.0000    0.5000
+     259 "C Fowlkes"                              0.0000    0.0000    0.5000
+     260 "J Roden"                                0.0000    0.0000    0.5000
+     261 "G Ridgeway"                             0.0000    0.0000    0.5000
+     262 "E Kuo"                                  0.0000    0.0000    0.5000
+     263 "L Su"                                   0.0000    0.0000    0.5000
+     264 "M Munich"                               0.0000    0.0000    0.5000
+     265 "D Golinelli"                            0.0000    0.0000    0.5000
+     266 "G Consonni"                             0.0000    0.0000    0.5000
+     267 "D Cunliffe"                             0.0000    0.0000    0.5000
+     268 "D Psaltis"                              0.0000    0.0000    0.5000
+     269 "G Steckman"                             0.0000    0.0000    0.5000
+     270 "P Courtier"                             0.0000    0.0000    0.5000
+     271 "M Latif"                                0.0000    0.0000    0.5000
+     272 "I Sim"                                  0.0000    0.0000    0.5000
+     273 "L Cordero"                              0.0000    0.0000    0.5000
+     274 "L Ugarte"                               0.0000    0.0000    0.5000
+     275 "K Pentikousis"                          0.0000    0.0000    0.5000
+     276 "N Kapadia"                              0.0000    0.0000    0.5000
+     277 "J seck"                                 0.0000    0.0000    0.5000
+     278 "F Lott"                                 0.0000    0.0000    0.5000
+     279 "D Chudova"                              0.0000    0.0000    0.5000
+     280 "P Yiou"                                 0.0000    0.0000    0.5000
+     281 "W Einhauser"                            0.0000    0.0000    0.5000
+     282 "P Vetter"                               0.0000    0.0000    0.5000
+     283 "S Goodbody"                             0.0000    0.0000    0.5000
+     284 "M Kawato"                               0.0000    0.0000    0.5000
+     285 "P Hrensen"                              0.0000    0.0000    0.5000
+     286 "M Levitz"                               0.0000    0.0000    0.5000
+*Edges
+      23     237       1
+     203     279       2
+     171     263       1
+     116     117       1
+      41     214       1
+      87     124       1
+      54     126       2
+     265     266       1
+      40      67       2
+       5      53      17
+     166     167       3
+      87     217       2
+      63      87       1
+      49     169       3
+      75      83       2
+      49      70       1
+      49      69       1
+      71     222       1
+     223     280       1
+      49     246       1
+     231     232       1
+      40     215       2
+       5     158       1
+      29      60       1
+      20     228       1
+      17      19       1
+      16     127       1
+     152     194       1
+      58      59       1
+     108     220       1
+      30     109       4
+      74     232       1
+      40     200       1
+      29     166       1
+      29     167       3
+      40     125       3
+      20      40       1
+      49     138       8
+      89     174       2
+      11      13       1
+      11      12       1
+     169     181       1
+      28      29       4
+      29     105       1
+      44     219       4
+      29      55       1
+      65     167       3
+     133     185       1
+      20      49       1
+       8     216       2
+       5     202       3
+     148     185       1
+      41     284       1
+      44     162       1
+      83     100       1
+      22      58       7
+      22      57       4
+      16      71       1
+      20      41       1
+     193     223       1
+     151     162       1
+     102     111       1
+     121     275       1
+     184     250       3
+      29      65      13
+      29      64       4
+      82     203       1
+      40     199       2
+     118     207       1
+      87     149      14
+     136     231       3
+      40     165       2
+      30     174       2
+      54     185       1
+      20     221       5
+      41     188       5
+       5      20       2
+      41     130       3
+       6     224       1
+     182     192       2
+     268     269       1
+     203     221       1
+      86      87       1
+     172     240       1
+       5     115       1
+     144     187       4
+     228     238       1
+     155     185       1
+      36     116       1
+      35     255       1
+     159     160       2
+     159     161       2
+     105     167       4
+      49     148       9
+     175     249       1
+      96     157       7
+      49     269       1
+     104     164       1
+       1     114       1
+      35     228       1
+      87     141       3
+      54      97       1
+     142     254       1
+     142     255       1
+      87     114       1
+      35     254       1
+       5     163       1
+       5     154       1
+     175     177       3
+     175     176       3
+     149     262       1
+     150     165       2
+     183     192       2
+      26      28       2
+      26      27       2
+      26      29       2
+      74     136       7
+     152     262       1
+      94     127       9
+      71     127       1
+      88     228       1
+     149     174       6
+      56     205       1
+       1     189       5
+     101     113       2
+      49     239       4
+      84     138       1
+     182     183       7
+      99     278       1
+      57      58       6
+     200     201       1
+      30      87       3
+     236     253       1
+      29     126       3
+     129     263       1
+     119     166       2
+      87     194       1
+      71     146       1
+      27      28       3
+      27      29       5
+      71      93       1
+      71      91       1
+      47     132       1
+      47     133       1
+      18      89       4
+      24      40       1
+      29     203       2
+      29      76       1
+     187     217       1
+     137     163       1
+     193     280       1
+       5      23       2
+     205     285       1
+      71      92       1
+      40     150       3
+     282     283       1
+     118     143       3
+      19     156       1
+      16      94       1
+     223     224       3
+      48      72       2
+       1     141       1
+       5     179       6
+       5     180       2
+       7       8       3
+      29      80       3
+      41      45      10
+      13     172       1
+     154     195       1
+      40     277       1
+      40      72       1
+      29      37       1
+      23     179       1
+      20     203       5
+     124     149       1
+     129     171       1
+      12      20       1
+      46     100       1
+      63      98       1
+      40     244       1
+     185     251       1
+      18      87       4
+      69      70       8
+     223     278       1
+      20     231       1
+     259     260       1
+      35      36       4
+       9     228       1
+     133     134       1
+     273     274       1
+      14      23       1
+      20     249       2
+      20      29       3
+      44     151       1
+      29     229       1
+      29     164       2
+     111     197       1
+      35     117       1
+      21      40       1
+       8      23       6
+      56     115       2
+      19     135       4
+      49     181       1
+      64      68       2
+      40     162       1
+      13     240       1
+      15     101       2
+      35     211       1
+      35     212       2
+      56     110       5
+      25      41       1
+      28      86       3
+      87     221       1
+      87     109       1
+       2     118       1
+       5      10       4
+     155     225       1
+      87      95       1
+      25      54       1
+     203     235       1
+      37      60       2
+      49      50       3
+       6     140       1
+      41     283       1
+      46      83       1
+      40     233       3
+      49      57       2
+      32      33      12
+      71      73       1
+      87     262       1
+      71     234       1
+      35     261       2
+      29     104       2
+      15      45       4
+      12      13       3
+      29     106       1
+      87     178       2
+      44     165       2
+      43     233       2
+       3     185       1
+      65     253       1
+      65     236       1
+      87      98       1
+      35     265       1
+      87      89       2
+     221     249       1
+     149     152       1
+     120     221       1
+       9      20       1
+      49      58       2
+      44     199       2
+     179     180       2
+      34     116       3
+      23     225       1
+      40     160       2
+      40     159       2
+      44     201       1
+     111     129       1
+      49      51       1
+       8     225       1
+     129     144       1
+      79     110       3
+       5     107       3
+      18      81       3
+      20     185       3
+      81     147       1
+     106     166       2
+      20     279       2
+      35     183       8
+      35     182       7
+     111     171       1
+      49     281       1
+     185     250       3
+      40      44      20
+      40      43       5
+      27      80       2
+      29      96       1
+     264     269       1
+     264     268       1
+     203     249       1
+     221     275       1
+     183     286       1
+      23      46       1
+      73     234       4
+      49     268       1
+       5      62       2
+      76     145       1
+      49     185       1
+       5     248       1
+     138     281       1
+     178     204       6
+      58     103       3
+     105     106       1
+      19      20       1
+     209     251       1
+      20      71       1
+      22     277       1
+     160     161       2
+      22     103       7
+      57      59       2
+      28      32       1
+     169     191       1
+     169     190       1
+      48      50       4
+      61      87       1
+      60      65       1
+      41     218       3
+       5     195       1
+       5     196       1
+      74     231       3
+      22      72       1
+      29      68       2
+     138     148       3
+       3      78       4
+      12     172       1
+      22     215       2
+      10      41       2
+       5     101       5
+      19     112       2
+     110     285       1
+     164     166       1
+       7      35       2
+     121     221       1
+       5     258       2
+      22     110       2
+     226     244       1
+      20     224       2
+     103     148       4
+     140     223       1
+      14     237       1
+      29     168       1
+      49      59       1
+      53     113       4
+      23     247       2
+      81      89       4
+      23     155      11
+     240     241       1
+      64     104       8
+     101     163       3
+      20     189       1
+       5      56       1
+      43      44       2
+      65     168       1
+       6     223       1
+      29      90       2
+      45      53       2
+     111     263       1
+      50      51       2
+       5     137       3
+     100     170       1
+      35     139       2
+     211     212       1
+      23     100       2
+      44     200       1
+      44     131       6
+      21      22       6
+      49     208       1
+      53      54       3
+     254     255       1
+     148     260       1
+     148     259       1
+     153     194       1
+      87     147       1
+      35     192       2
+      23     216       5
+     181     245       1
+      49     191       1
+      49     190       1
+      19     276       2
+       4      83       2
+      92      93       3
+      49     103       4
+      89     147       1
+      71     198       1
+     136     232       1
+       5      45      21
+      64     167       4
+      19     123       7
+      40     151       1
+      20     118       1
+      35     286       1
+     112     276       2
+      40      77       2
+     239     281       1
+      81      87       1
+      81      86       1
+      23     170       2
+       5     110       1
+     185     209       1
+      91      93       3
+      91      92       3
+      82     235       1
+      29     236       1
+     155     216       1
+      40     243       3
+      40     122       2
+      53     179       4
+     107     154       1
+     173     247       2
+     110     205       2
+     104     106       1
+     184     185       5
+      41     206       1
+      87     144       4
+      34     117       1
+     107     196       1
+      30     124       4
+      23     173       4
+     118     252       1
+       5     205       1
+      67      77       2
+      41     186       3
+     120     275       1
+     215     277       1
+     143     252       1
+      20     148       1
+      18     152       1
+      20     223       2
+      64     105      10
+      99     271       1
+      23     227       1
+      29     157       2
+       2     273       1
+       2     274       1
+      20     175       5
+      20     176       3
+      20     177       3
+      61      89       6
+      49     256       1
+     177     220       1
+      41      75       1
+      20     133       1
+      87     152       2
+       5      79       1
+      78     155       1
+      20      23       3
+      35     116       4
+      22      40       6
+      35     266       1
+      43     199       1
+     116     182       1
+     116     183       1
+      85     136       4
+     140     224       1
+      66     111       1
+     107     195       3
+      29     253       1
+      34      36       4
+      34      35       7
+       5      15       3
+       5     257       1
+      87     153       1
+      74      85       5
+     118     213       1
+     106     164       1
+       4     231       1
+     229     230       1
+      41     282       1
+     138     239       4
+      29      32       3
+      29      33       2
+      44     150       3
+      40      71       1
+     104     167       3
+      40     201       1
+      53     101       7
+     221     231       1
+     131     219       1
+      23      83       1
+      45     113       4
+     231     272       1
+      64      65       2
+     190     191       1
+      90     126       2
+      71      94       1
+      29     145       1
+       3     184       8
+      29     230       1
+      34     139       1
+       5     285       1
+      40     219       4
+      22     125       6
+      72     215       1
+      23     210       1
+      20      35       1
+      31      87       1
+      31      86       1
+      53     258       1
+     152     153       5
+      85     231       1
+      41      42       8
+      65     229       1
+      65     230       1
+      35     142       1
+     120     121       6
+     169     245       2
+      61      81       1
+      40      52       1
+      30     149       2
+      87     204       3
+      49     264       5
+      40     161       2
+       7     261       2
+      12     111       1
+      20      87       1
+      44     243       3
+      99     223       2
+      97     185       1
+      35     119       1
+       8     227       1
+      12     240       2
+      12     241       1
+      39      40       1
+       8     155       2
+     216     227       1
+     102     197       1
+      17     135       1
+      78     185       1
+     104     105      10
+     202     258       4
+     103     138       1
+       5     113      12
+     108     177       7
+      48      49      11
+      48      51       2
+     224     270       1
+      87     174       4
+       5      41      10
+      20     111       2
+      40     226       1
+     119     139       1
+     223     271       1
+      87     187       3
+       9      35       1
+      49     245       2
+     118     274       1
+     118     273       1
+       8     170       1
+     176     177       3
+      49      84       9
+      37      65       1
+     114     141       1
+      40     267       1
+     118     185       1
+      41     242       1
+     132     133       3
+     132     134       1
+      53     202       1
+     144     217       2
+      40     131       6
+     188     218       2
+      87     128       2
+      38      39       1
+      38      40       1
diff --git a/jung-samples/src/main/resources/datasets/weighted.net b/jung-samples/src/main/resources/datasets/weighted.net
new file mode 100644
index 0000000..8cb24aa
--- /dev/null
+++ b/jung-samples/src/main/resources/datasets/weighted.net
@@ -0,0 +1,8 @@
+*Vertices 3
+1 "in1"
+2 "in2"
+3 "in3"
+*Edges
+1 2 10 10
+1 3 1 1
+2 3 5 5
diff --git a/jung-samples/src/main/resources/datasets/zachary.net b/jung-samples/src/main/resources/datasets/zachary.net
new file mode 100644
index 0000000..f6b1ba2
--- /dev/null
+++ b/jung-samples/src/main/resources/datasets/zachary.net
@@ -0,0 +1,114 @@
+*Vertices      34
+       1 "1"                                      0.6680    0.3596    0.5000
+       2 "2"                                      0.7146    0.5745    0.5000
+       3 "3"                                      0.5470    0.4607    0.5000
+       4 "4"                                      0.7270    0.4813    0.5000
+       5 "5"                                      0.7150    0.1929    0.5000
+       6 "6"                                      0.7813    0.1330    0.5000
+       7 "7"                                      0.8013    0.1929    0.5000
+       8 "8"                                      0.7424    0.5463    0.5000
+       9 "9"                                      0.5510    0.5262    0.5000
+      10 "10"                                     0.4630    0.5356    0.5000
+      11 "11"                                     0.6873    0.1338    0.5000
+      12 "12"                                     0.8413    0.3596    0.5000
+      13 "13"                                     0.8036    0.4532    0.5000
+      14 "14"                                     0.6116    0.5443    0.5000
+      15 "15"                                     0.2306    0.8916    0.5000
+      16 "16"                                     0.3470    0.9505    0.5000
+      17 "17"                                     0.8663    0.1330    0.5000
+      18 "18"                                     0.8100    0.5468    0.5000
+      19 "19"                                     0.2077    0.8501    0.5000
+      20 "20"                                     0.6283    0.5955    0.5000
+      21 "21"                                     0.3115    0.9469    0.5000
+      22 "22"                                     0.7942    0.5784    0.5000
+      23 "23"                                     0.2660    0.9264    0.5000
+      24 "24"                                     0.2383    0.5472    0.5000
+      25 "25"                                     0.2823    0.2970    0.5000
+      26 "26"                                     0.2306    0.3616    0.5000
+      27 "27"                                     0.1850    0.7828    0.5000
+      28 "28"                                     0.3373    0.4704    0.5000
+      29 "29"                                     0.3827    0.5160    0.5000
+      30 "30"                                     0.2060    0.6628    0.5000
+      31 "31"                                     0.5619    0.7032    0.5000
+      32 "32"                                     0.4127    0.4366    0.5000
+      33 "33"                                     0.4016    0.6960    0.5000
+      34 "34"                                     0.3830    0.6161    0.5000
+*Edges
+       1       2        2
+       1       3        2
+       1       4        2
+       1       5        2
+       1       6        2
+       1       7        2
+       1       8        2
+       1       9        2
+       1      11        2
+       1      12        2
+       1      13        2
+       1      14        2
+       1      18        2
+       1      20        2
+       1      22        2
+       1      32        2
+       2       3        2
+       2       4        2
+       2       8        2
+       2      14        2
+       2      18        2
+       2      20        2
+       2      22        2
+       2      31        2
+       3       4        2
+       3       8        2
+       3       9        2
+       3      10        2
+       3      14        2
+       3      28        2
+       3      29        2
+       3      33        2
+       4       8        2
+       4      13        2
+       4      14        2
+       5       7        2
+       5      11        2
+       6       7        2
+       6      11        2
+       6      17        2
+       7      17        2
+       9      31        2
+       9      33        2
+       9      34        2
+      10      34        2
+      14      34        2
+      15      33        2
+      15      34        2
+      16      33        2
+      16      34        2
+      19      33        2
+      19      34        2
+      20      34        2
+      21      33        2
+      21      34        2
+      23      33        2
+      23      34        2
+      24      26        2
+      24      28        2
+      24      30        2
+      24      33        2
+      24      34        2
+      25      26        2
+      25      28        2
+      25      32        2
+      26      32        2
+      27      30        2
+      27      34        2
+      28      34        2
+      29      32        2
+      29      34        2
+      30      33        2
+      30      34        2
+      31      33        2
+      31      34        2
+      32      33        2
+      32      34        2
+      33      34        2
diff --git a/jung-samples/src/main/resources/images/Sandstone.jpg b/jung-samples/src/main/resources/images/Sandstone.jpg
new file mode 100644
index 0000000..9ab395a
Binary files /dev/null and b/jung-samples/src/main/resources/images/Sandstone.jpg differ
diff --git a/jung-samples/src/main/resources/images/china.gif b/jung-samples/src/main/resources/images/china.gif
new file mode 100644
index 0000000..4e598ec
Binary files /dev/null and b/jung-samples/src/main/resources/images/china.gif differ
diff --git a/jung-samples/src/main/resources/images/france.gif b/jung-samples/src/main/resources/images/france.gif
new file mode 100644
index 0000000..e32e0e4
Binary files /dev/null and b/jung-samples/src/main/resources/images/france.gif differ
diff --git a/jung-samples/src/main/resources/images/germany.gif b/jung-samples/src/main/resources/images/germany.gif
new file mode 100644
index 0000000..9802dc6
Binary files /dev/null and b/jung-samples/src/main/resources/images/germany.gif differ
diff --git a/jung-samples/src/main/resources/images/japan.gif b/jung-samples/src/main/resources/images/japan.gif
new file mode 100644
index 0000000..085340c
Binary files /dev/null and b/jung-samples/src/main/resources/images/japan.gif differ
diff --git a/jung-samples/src/main/resources/images/lightning-s.gif b/jung-samples/src/main/resources/images/lightning-s.gif
new file mode 100644
index 0000000..d85af31
Binary files /dev/null and b/jung-samples/src/main/resources/images/lightning-s.gif differ
diff --git a/jung-samples/src/main/resources/images/political_world_map.jpg b/jung-samples/src/main/resources/images/political_world_map.jpg
new file mode 100644
index 0000000..ce25b1f
Binary files /dev/null and b/jung-samples/src/main/resources/images/political_world_map.jpg differ
diff --git a/jung-samples/src/main/resources/images/russia.gif b/jung-samples/src/main/resources/images/russia.gif
new file mode 100644
index 0000000..ac28d34
Binary files /dev/null and b/jung-samples/src/main/resources/images/russia.gif differ
diff --git a/jung-samples/src/main/resources/images/spain.gif b/jung-samples/src/main/resources/images/spain.gif
new file mode 100644
index 0000000..8bc2878
Binary files /dev/null and b/jung-samples/src/main/resources/images/spain.gif differ
diff --git a/jung-samples/src/main/resources/images/topicapple.gif b/jung-samples/src/main/resources/images/topicapple.gif
new file mode 100644
index 0000000..55844c8
Binary files /dev/null and b/jung-samples/src/main/resources/images/topicapple.gif differ
diff --git a/jung-samples/src/main/resources/images/topicgamespcgames.gif b/jung-samples/src/main/resources/images/topicgamespcgames.gif
new file mode 100644
index 0000000..06752e2
Binary files /dev/null and b/jung-samples/src/main/resources/images/topicgamespcgames.gif differ
diff --git a/jung-samples/src/main/resources/images/topicgraphics3.gif b/jung-samples/src/main/resources/images/topicgraphics3.gif
new file mode 100644
index 0000000..0568531
Binary files /dev/null and b/jung-samples/src/main/resources/images/topicgraphics3.gif differ
diff --git a/jung-samples/src/main/resources/images/topichumor.gif b/jung-samples/src/main/resources/images/topichumor.gif
new file mode 100644
index 0000000..467b566
Binary files /dev/null and b/jung-samples/src/main/resources/images/topichumor.gif differ
diff --git a/jung-samples/src/main/resources/images/topicinputdevices.gif b/jung-samples/src/main/resources/images/topicinputdevices.gif
new file mode 100644
index 0000000..4d06c64
Binary files /dev/null and b/jung-samples/src/main/resources/images/topicinputdevices.gif differ
diff --git a/jung-samples/src/main/resources/images/topiclinux.gif b/jung-samples/src/main/resources/images/topiclinux.gif
new file mode 100644
index 0000000..5fdf70c
Binary files /dev/null and b/jung-samples/src/main/resources/images/topiclinux.gif differ
diff --git a/jung-samples/src/main/resources/images/topicmusic.gif b/jung-samples/src/main/resources/images/topicmusic.gif
new file mode 100644
index 0000000..3bdb9f7
Binary files /dev/null and b/jung-samples/src/main/resources/images/topicmusic.gif differ
diff --git a/jung-samples/src/main/resources/images/topicos.gif b/jung-samples/src/main/resources/images/topicos.gif
new file mode 100644
index 0000000..0dea388
Binary files /dev/null and b/jung-samples/src/main/resources/images/topicos.gif differ
diff --git a/jung-samples/src/main/resources/images/topicprivacy.gif b/jung-samples/src/main/resources/images/topicprivacy.gif
new file mode 100644
index 0000000..14f936f
Binary files /dev/null and b/jung-samples/src/main/resources/images/topicprivacy.gif differ
diff --git a/jung-samples/src/main/resources/images/topicsample.gif b/jung-samples/src/main/resources/images/topicsample.gif
new file mode 100644
index 0000000..c9a4ec8
Binary files /dev/null and b/jung-samples/src/main/resources/images/topicsample.gif differ
diff --git a/jung-samples/src/main/resources/images/topicsample2.gif b/jung-samples/src/main/resources/images/topicsample2.gif
new file mode 100644
index 0000000..8110286
Binary files /dev/null and b/jung-samples/src/main/resources/images/topicsample2.gif differ
diff --git a/jung-samples/src/main/resources/images/topicwireless.gif b/jung-samples/src/main/resources/images/topicwireless.gif
new file mode 100644
index 0000000..897ac4e
Binary files /dev/null and b/jung-samples/src/main/resources/images/topicwireless.gif differ
diff --git a/jung-samples/src/main/resources/images/topicx.gif b/jung-samples/src/main/resources/images/topicx.gif
new file mode 100644
index 0000000..850b87c
Binary files /dev/null and b/jung-samples/src/main/resources/images/topicx.gif differ
diff --git a/jung-samples/src/main/resources/images/united-states.gif b/jung-samples/src/main/resources/images/united-states.gif
new file mode 100644
index 0000000..a660b8a
Binary files /dev/null and b/jung-samples/src/main/resources/images/united-states.gif differ
diff --git a/jung-samples/src/site/site.xml b/jung-samples/src/site/site.xml
new file mode 100644
index 0000000..4f25bdf
--- /dev/null
+++ b/jung-samples/src/site/site.xml
@@ -0,0 +1,14 @@
+<project name="${project.name}">
+  <bannerLeft>
+    <name>${project.name}</name>
+  </bannerLeft>
+  <body>
+    <links>
+      <item name="${project.name}" href="${project.url}"/>
+    </links>
+    <menu ref="parent" />
+    <menu ref="modules" />
+    <menu ref="reports" />
+  </body>
+</project>
+
diff --git a/jung-visualization-2.0.1-sources.jar b/jung-visualization-2.0.1-sources.jar
deleted file mode 100644
index 1ecf3e8..0000000
Binary files a/jung-visualization-2.0.1-sources.jar and /dev/null differ
diff --git a/jung-visualization/assembly.xml b/jung-visualization/assembly.xml
new file mode 100644
index 0000000..b1ae6a3
--- /dev/null
+++ b/jung-visualization/assembly.xml
@@ -0,0 +1,14 @@
+<assembly>
+  <id>dependencies</id>
+  <formats>
+    <format>tar.gz</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <dependencySets>
+    <dependencySet>
+      <outputDirectory>/</outputDirectory>
+      <unpack>false</unpack>
+      <scope>runtime</scope>
+    </dependencySet>
+  </dependencySets>
+</assembly>
diff --git a/jung-visualization/pom.xml b/jung-visualization/pom.xml
new file mode 100644
index 0000000..893d13e
--- /dev/null
+++ b/jung-visualization/pom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>net.sf.jung</groupId>
+    <artifactId>jung-parent</artifactId>
+    <version>2.1.1</version>
+  </parent>
+  <artifactId>jung-visualization</artifactId>
+  <name>JUNG - Visualization Support</name>
+  <description>Core visualization support for the JUNG project</description>
+
+  <dependencies>
+    <dependency>
+      <groupId>net.sf.jung</groupId>
+      <artifactId>jung-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>net.sf.jung</groupId>
+      <artifactId>jung-algorithms</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>net.sf.jung</groupId>
+      <artifactId>jung-graph-impl</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/BasicTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/BasicTransformer.java
new file mode 100644
index 0000000..c621fd1
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/BasicTransformer.java
@@ -0,0 +1,173 @@
+package edu.uci.ics.jung.visualization;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import edu.uci.ics.jung.visualization.transform.MutableAffineTransformer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer;
+import edu.uci.ics.jung.visualization.util.ChangeEventSupport;
+import edu.uci.ics.jung.visualization.util.DefaultChangeEventSupport;
+
+/**
+ * A basic implementation of the MultiLayerTransformer interface that 
+ * provides two Layers: VIEW and LAYOUT. It also provides ChangeEventSupport
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ */
+public class BasicTransformer implements MultiLayerTransformer, 
+	ShapeTransformer, ChangeListener, ChangeEventSupport  {
+
+    protected ChangeEventSupport changeSupport =
+        new DefaultChangeEventSupport(this);
+
+    protected MutableTransformer viewTransformer = 
+        new MutableAffineTransformer(new AffineTransform());
+    
+    protected MutableTransformer layoutTransformer =
+        new MutableAffineTransformer(new AffineTransform());
+
+    /**
+     * Creates an instance and notifies the view and layout Functions to listen to
+     * changes published by this instance.
+     */
+    public BasicTransformer() {
+		super();
+		viewTransformer.addChangeListener(this);
+		layoutTransformer.addChangeListener(this);
+	}
+
+    protected void setViewTransformer(MutableTransformer Function) {
+        this.viewTransformer.removeChangeListener(this);
+        this.viewTransformer = Function;
+        this.viewTransformer.addChangeListener(this);
+    }
+
+    protected void setLayoutTransformer(MutableTransformer Function) {
+        this.layoutTransformer.removeChangeListener(this);
+        this.layoutTransformer = Function;
+        this.layoutTransformer.addChangeListener(this);
+    }
+
+
+	protected MutableTransformer getLayoutTransformer() {
+		return layoutTransformer;
+	}
+
+	protected MutableTransformer getViewTransformer() {
+		return viewTransformer;
+	}
+
+	public Point2D inverseTransform(Point2D p) {
+	    return inverseLayoutTransform(inverseViewTransform(p));
+	}
+	
+	protected Point2D inverseViewTransform(Point2D p) {
+	    return viewTransformer.inverseTransform(p);
+	}
+
+    protected Point2D inverseLayoutTransform(Point2D p) {
+        return layoutTransformer.inverseTransform(p);
+    }
+
+	public Point2D transform(Point2D p) {
+	    return viewTransform(layoutTransform(p));
+	}
+    
+    protected Point2D viewTransform(Point2D p) {
+        return viewTransformer.transform(p);
+    }
+    
+    protected Point2D layoutTransform(Point2D p) {
+        return layoutTransformer.transform(p);
+    }
+    
+	public Shape inverseTransform(Shape shape) {
+	    return inverseLayoutTransform(inverseViewTransform(shape));
+	}
+	
+	protected Shape inverseViewTransform(Shape shape) {
+	    return viewTransformer.inverseTransform(shape);
+	}
+
+    protected Shape inverseLayoutTransform(Shape shape) {
+        return layoutTransformer.inverseTransform(shape);
+    }
+
+	public Shape transform(Shape shape) {
+	    return viewTransform(layoutTransform(shape));
+	}
+    
+    protected Shape viewTransform(Shape shape) {
+        return viewTransformer.transform(shape);
+    }
+    
+    protected Shape layoutTransform(Shape shape) {
+        return layoutTransformer.transform(shape);
+    }
+    
+    public void setToIdentity() {
+    	layoutTransformer.setToIdentity();
+    	viewTransformer.setToIdentity();
+    }
+    
+    public void addChangeListener(ChangeListener l) {
+        changeSupport.addChangeListener(l);
+    }
+    
+    public void removeChangeListener(ChangeListener l) {
+        changeSupport.removeChangeListener(l);
+    }
+    
+    public ChangeListener[] getChangeListeners() {
+        return changeSupport.getChangeListeners();
+    }
+
+    public void fireStateChanged() {
+        changeSupport.fireStateChanged();
+    }   
+    
+	public void stateChanged(ChangeEvent e) {
+	    fireStateChanged();
+	}
+
+	public MutableTransformer getTransformer(Layer layer) {
+		if(layer == Layer.LAYOUT) return layoutTransformer;
+		if(layer == Layer.VIEW) return viewTransformer;
+		return null;
+	}
+
+	public Point2D inverseTransform(Layer layer, Point2D p) {
+		if(layer == Layer.LAYOUT) return inverseLayoutTransform(p);
+		if(layer == Layer.VIEW) return inverseViewTransform(p);
+		return null;
+	}
+
+	public void setTransformer(Layer layer, MutableTransformer Function) {
+		if(layer == Layer.LAYOUT) setLayoutTransformer(Function);
+		if(layer == Layer.VIEW) setViewTransformer(Function);
+		
+	}
+
+	public Point2D transform(Layer layer, Point2D p) {
+		if(layer == Layer.LAYOUT) return layoutTransform(p);
+		if(layer == Layer.VIEW) return viewTransform(p);
+		return null;
+	}
+
+	public Shape transform(Layer layer, Shape shape) {
+		if(layer == Layer.LAYOUT) return layoutTransform(shape);
+		if(layer == Layer.VIEW) return viewTransform(shape);
+		return null;
+	}
+	
+	public Shape inverseTransform(Layer layer, Shape shape) {
+		if(layer == Layer.LAYOUT) return inverseLayoutTransform(shape);
+		if(layer == Layer.VIEW) return inverseViewTransform(shape);
+		return null;
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/BasicVisualizationServer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/BasicVisualizationServer.java
new file mode 100644
index 0000000..b30991e
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/BasicVisualizationServer.java
@@ -0,0 +1,508 @@
+/*
+\* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.visualization;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.RenderingHints.Key;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JPanel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.visualization.control.ScalingControl;
+import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
+import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
+import edu.uci.ics.jung.visualization.picking.MultiPickedState;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.picking.ShapePickSupport;
+import edu.uci.ics.jung.visualization.renderers.BasicRenderer;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
+import edu.uci.ics.jung.visualization.util.Caching;
+import edu.uci.ics.jung.visualization.util.ChangeEventSupport;
+import edu.uci.ics.jung.visualization.util.DefaultChangeEventSupport;
+
+/**
+ * A class that maintains many of the details necessary for creating 
+ * visualizations of graphs.
+ * This is the old VisualizationViewer without tooltips and mouse behaviors. Its purpose is
+ * to be a base class that can also be used on the server side of a multi-tiered application.
+ * 
+ * @author Joshua O'Madadhain
+ * @author Tom Nelson
+ * @author Danyel Fisher
+ */
+ at SuppressWarnings("serial")
+public class BasicVisualizationServer<V, E> extends JPanel 
+                implements ChangeListener, ChangeEventSupport, VisualizationServer<V, E>{
+
+    protected ChangeEventSupport changeSupport =
+        new DefaultChangeEventSupport(this);
+    
+    /**
+     * holds the state of this View
+     */
+    protected VisualizationModel<V,E> model;
+
+	/**
+	 * handles the actual drawing of graph elements
+	 */
+	protected Renderer<V,E> renderer = new BasicRenderer<V,E>();
+	
+	/**
+	 * rendering hints used in drawing. Anti-aliasing is on
+	 * by default
+	 */
+	protected Map<Key, Object> renderingHints = new HashMap<Key, Object>();
+		
+	/**
+	 * holds the state of which vertices of the graph are
+	 * currently 'picked'
+	 */
+	protected PickedState<V> pickedVertexState;
+	
+	/**
+	 * holds the state of which edges of the graph are
+	 * currently 'picked'
+	 */
+    protected PickedState<E> pickedEdgeState;
+    
+    /**
+     * a listener used to cause pick events to result in
+     * repaints, even if they come from another view
+     */
+    protected ItemListener pickEventListener;
+	
+	/**
+	 * an offscreen image to render the graph
+	 * Used if doubleBuffered is set to true
+	 */
+	protected BufferedImage offscreen;
+	
+	/**
+	 * graphics context for the offscreen image
+	 * Used if doubleBuffered is set to true
+	 */
+	protected Graphics2D offscreenG2d;
+	
+	/**
+	 * user-settable choice to use the offscreen image
+	 * or not. 'false' by default
+	 */
+	protected boolean doubleBuffered;
+	
+	/**
+	 * a collection of user-implementable functions to render under
+	 * the topology (before the graph is rendered)
+	 */
+	protected List<Paintable> preRenderers = new ArrayList<Paintable>();
+	
+	/**
+	 * a collection of user-implementable functions to render over the
+	 * topology (after the graph is rendered)
+	 */
+	protected List<Paintable> postRenderers = new ArrayList<Paintable>();
+	
+    protected RenderContext<V,E> renderContext;
+    
+    /**
+     * Create an instance with the specified Layout.
+     * 
+     * @param layout		The Layout to apply, with its associated Graph
+     */
+	public BasicVisualizationServer(Layout<V,E> layout) {
+	    this(new DefaultVisualizationModel<V,E>(layout));
+	}
+	
+    /**
+     * Create an instance with the specified Layout and view dimension.
+     * 
+     * @param layout		The Layout to apply, with its associated Graph
+     * @param preferredSize the preferred size of this View
+     */
+	public BasicVisualizationServer(Layout<V,E> layout, Dimension preferredSize) {
+	    this(new DefaultVisualizationModel<V,E>(layout, preferredSize), preferredSize);
+	}
+	
+	/**
+	 * Create an instance with the specified model and a default dimension (600x600).
+	 * 
+	 * @param model the model to use
+	 */
+	public BasicVisualizationServer(VisualizationModel<V,E> model) {
+	    this(model, new Dimension(600,600));
+	}
+	
+	/**
+	 * Create an instance with the specified model and view dimension.
+	 * 
+	 * @param model the model to use
+	 * @param preferredSize initial preferred size of the view
+	 */
+    public BasicVisualizationServer(VisualizationModel<V,E> model,
+	        Dimension preferredSize) {
+	    this.model = model;
+	    renderContext = new PluggableRenderContext<V,E>(model.getGraphLayout().getGraph());
+	    model.addChangeListener(this);
+	    setDoubleBuffered(false);
+		this.addComponentListener(new VisualizationListener(this));
+
+		setPickSupport(new ShapePickSupport<V,E>(this));
+		setPickedVertexState(new MultiPickedState<V>());
+		setPickedEdgeState(new MultiPickedState<E>());
+        
+        renderContext.setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<E>(getPickedEdgeState(), Color.black, Color.cyan));
+        renderContext.setVertexFillPaintTransformer(new PickableVertexPaintTransformer<V>(getPickedVertexState(), 
+                Color.red, Color.yellow));
+		
+		setPreferredSize(preferredSize);
+		renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+        renderContext.getMultiLayerTransformer().addChangeListener(this);
+	}
+	
+	@Override
+    public void setDoubleBuffered(boolean doubleBuffered) {
+	    this.doubleBuffered = doubleBuffered;
+	}
+	
+	@Override
+    public boolean isDoubleBuffered() {
+	    return doubleBuffered;
+	}
+	
+	/**
+	 * Always sanity-check getSize so that we don't use a
+	 * value that is improbable
+	 * @see java.awt.Component#getSize()
+	 */
+	@Override
+	public Dimension getSize() {
+		Dimension d = super.getSize();
+		if(d.width <= 0 || d.height <= 0) {
+			d = getPreferredSize();
+		}
+		return d;
+	}
+
+	/**
+	 * Ensure that, if doubleBuffering is enabled, the offscreen
+	 * image buffer exists and is the correct size.
+	 * @param d the expected Dimension of the offscreen buffer
+	 */
+	protected void checkOffscreenImage(Dimension d) {
+	    if(doubleBuffered) {
+	        if(offscreen == null || offscreen.getWidth() != d.width || offscreen.getHeight() != d.height) {
+	            offscreen = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB);
+	            offscreenG2d = offscreen.createGraphics();
+	        }
+	    }
+	}
+	
+    public VisualizationModel<V,E> getModel() {
+        return model;
+    }
+
+    public void setModel(VisualizationModel<V,E> model) {
+        this.model = model;
+    }
+
+	public void stateChanged(ChangeEvent e) {
+	    repaint();
+	    fireStateChanged();
+	}
+
+	public void setRenderer(Renderer<V,E> r) {
+	    this.renderer = r;
+	    repaint();
+	}
+	
+	public Renderer<V,E> getRenderer() {
+	    return renderer;
+	}
+
+    public void setGraphLayout(Layout<V,E> layout) {
+    	Dimension viewSize = getPreferredSize();
+    	if(this.isShowing()) {
+    		viewSize = getSize();
+    	}
+	    model.setGraphLayout(layout, viewSize);
+    }
+    
+    public void scaleToLayout(ScalingControl scaler) {
+    	Dimension vd = getPreferredSize();
+    	if(this.isShowing()) {
+    		vd = getSize();
+    	}
+		Dimension ld = getGraphLayout().getSize();
+		if(vd.equals(ld) == false) {
+			scaler.scale(this, (float)(vd.getWidth()/ld.getWidth()), new Point2D.Double());
+		}
+    }
+	
+	public Layout<V,E> getGraphLayout() {
+	        return model.getGraphLayout();
+	}
+	
+	@Override
+    public void setVisible(boolean aFlag) {
+		super.setVisible(aFlag);
+		if(aFlag == true) {
+			Dimension d = this.getSize();
+			if(d.width <= 0 || d.height <= 0) {
+				d = this.getPreferredSize();
+			}
+			model.getGraphLayout().setSize(d);
+		}
+	}
+
+    public Map<Key, Object> getRenderingHints() {
+        return renderingHints;
+    }
+    
+    public void setRenderingHints(Map<Key, Object> renderingHints) {
+        this.renderingHints = renderingHints;
+    }
+    
+	@Override
+    protected void paintComponent(Graphics g) {
+        super.paintComponent(g);
+
+		Graphics2D g2d = (Graphics2D)g;
+		if(doubleBuffered) {
+		    checkOffscreenImage(getSize());
+			renderGraph(offscreenG2d);
+		    g2d.drawImage(offscreen, null, 0, 0);
+		} else {
+		    renderGraph(g2d);
+		}
+	}
+	
+	protected void renderGraph(Graphics2D g2d) {
+	    if(renderContext.getGraphicsContext() == null) {
+	        renderContext.setGraphicsContext(new GraphicsDecorator(g2d));
+        } else {
+        	renderContext.getGraphicsContext().setDelegate(g2d);
+        }
+        renderContext.setScreenDevice(this);
+	    Layout<V,E> layout = model.getGraphLayout();
+
+		g2d.setRenderingHints(renderingHints);
+		
+		// the size of the VisualizationViewer
+		Dimension d = getSize();
+		
+		// clear the offscreen image
+		g2d.setColor(getBackground());
+		g2d.fillRect(0,0,d.width,d.height);
+
+		AffineTransform oldXform = g2d.getTransform();
+        AffineTransform newXform = new AffineTransform(oldXform);
+        newXform.concatenate(
+        		renderContext.getMultiLayerTransformer().getTransformer(Layer.VIEW).getTransform());
+		
+        g2d.setTransform(newXform);
+
+		// if there are  preRenderers set, paint them
+		for(Paintable paintable : preRenderers) {
+
+		    if(paintable.useTransform()) {
+		        paintable.paint(g2d);
+		    } else {
+		        g2d.setTransform(oldXform);
+		        paintable.paint(g2d);
+                g2d.setTransform(newXform);
+		    }
+		}
+		
+        if(layout instanceof Caching) {
+        	((Caching)layout).clear();
+        }
+        
+        renderer.render(renderContext, layout);
+		
+		// if there are postRenderers set, do it
+		for(Paintable paintable : postRenderers) {
+
+		    if(paintable.useTransform()) {
+		        paintable.paint(g2d);
+		    } else {
+		        g2d.setTransform(oldXform);
+		        paintable.paint(g2d);
+                g2d.setTransform(newXform);
+		    }
+		}
+		g2d.setTransform(oldXform);
+	}
+
+	/**
+	 * VisualizationListener reacts to changes in the size of the
+	 * VisualizationViewer. When the size changes, it ensures
+	 * that the offscreen image is sized properly. 
+	 * If the layout is locked to this view size, then the layout
+	 * is also resized to be the same as the view size.
+	 *
+	 *
+	 */
+	protected class VisualizationListener extends ComponentAdapter {
+		protected BasicVisualizationServer<V,E> vv;
+		public VisualizationListener(BasicVisualizationServer<V,E> vv) {
+			this.vv = vv;
+		}
+
+		/**
+		 * create a new offscreen image for the graph
+		 * whenever the window is resied
+		 */
+		@Override
+        public void componentResized(ComponentEvent e) {
+		    Dimension d = vv.getSize();
+		    if(d.width <= 0 || d.height <= 0) return;
+		    checkOffscreenImage(d);
+		    repaint();
+		}
+	}
+
+    public void addPreRenderPaintable(Paintable paintable) {
+        if(preRenderers == null) {
+            preRenderers = new ArrayList<Paintable>();
+        }
+        preRenderers.add(paintable);
+    }
+    
+    public void prependPreRenderPaintable(Paintable paintable) {
+        if(preRenderers == null) {
+            preRenderers = new ArrayList<Paintable>();
+        }
+        preRenderers.add(0,paintable);
+    }
+    
+    public void removePreRenderPaintable(Paintable paintable) {
+        if(preRenderers != null) {
+            preRenderers.remove(paintable);
+        }
+    }
+    
+    public void addPostRenderPaintable(Paintable paintable) {
+        if(postRenderers == null) {
+            postRenderers = new ArrayList<Paintable>();
+        }
+        postRenderers.add(paintable);
+    }
+    
+    public void prependPostRenderPaintable(Paintable paintable) {
+        if(postRenderers == null) {
+            postRenderers = new ArrayList<Paintable>();
+        }
+        postRenderers.add(0,paintable);
+    }
+    
+    public void removePostRenderPaintable(Paintable paintable) {
+        if(postRenderers != null) {
+            postRenderers.remove(paintable);
+        }
+    }
+
+    public void addChangeListener(ChangeListener l) {
+        changeSupport.addChangeListener(l);
+    }
+    
+    public void removeChangeListener(ChangeListener l) {
+        changeSupport.removeChangeListener(l);
+    }
+    
+    public ChangeListener[] getChangeListeners() {
+        return changeSupport.getChangeListeners();
+    }
+
+    public void fireStateChanged() {
+        changeSupport.fireStateChanged();
+    }   
+    
+    public PickedState<V> getPickedVertexState() {
+        return pickedVertexState;
+    }
+
+    public PickedState<E> getPickedEdgeState() {
+        return pickedEdgeState;
+    }
+    
+    public void setPickedVertexState(PickedState<V> pickedVertexState) {
+        if(pickEventListener != null && this.pickedVertexState != null) {
+            this.pickedVertexState.removeItemListener(pickEventListener);
+        }
+        this.pickedVertexState = pickedVertexState;
+        this.renderContext.setPickedVertexState(pickedVertexState);
+        if(pickEventListener == null) {
+            pickEventListener = new ItemListener() {
+
+                public void itemStateChanged(ItemEvent e) {
+                    repaint();
+                }
+            };
+        }
+        pickedVertexState.addItemListener(pickEventListener);
+    }
+    
+    public void setPickedEdgeState(PickedState<E> pickedEdgeState) {
+        if(pickEventListener != null && this.pickedEdgeState != null) {
+            this.pickedEdgeState.removeItemListener(pickEventListener);
+        }
+        this.pickedEdgeState = pickedEdgeState;
+        this.renderContext.setPickedEdgeState(pickedEdgeState);
+        if(pickEventListener == null) {
+            pickEventListener = new ItemListener() {
+
+                public void itemStateChanged(ItemEvent e) {
+                    repaint();
+                }
+            };
+        }
+        pickedEdgeState.addItemListener(pickEventListener);
+    }
+    
+    public GraphElementAccessor<V,E> getPickSupport() {
+        return renderContext.getPickSupport();
+    }
+
+    public void setPickSupport(GraphElementAccessor<V,E> pickSupport) {
+        renderContext.setPickSupport(pickSupport);
+    }
+    
+    public Point2D getCenter() {
+        Dimension d = getSize();
+        return new Point2D.Float(d.width/2, d.height/2);
+    }
+
+    public RenderContext<V,E> getRenderContext() {
+        return renderContext;
+    }
+
+    public void setRenderContext(RenderContext<V,E> renderContext) {
+        this.renderContext = renderContext;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/DefaultVisualizationModel.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/DefaultVisualizationModel.java
new file mode 100644
index 0000000..ad9fcaa
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/DefaultVisualizationModel.java
@@ -0,0 +1,188 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.visualization;
+
+import java.awt.Dimension;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.EventListenerList;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.util.Relaxer;
+import edu.uci.ics.jung.algorithms.layout.util.VisRunner;
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.visualization.layout.ObservableCachingLayout;
+import edu.uci.ics.jung.visualization.util.ChangeEventSupport;
+import edu.uci.ics.jung.visualization.util.DefaultChangeEventSupport;
+
+/**
+ * The model containing state values for 
+ * visualizations of graphs. 
+ * Refactored and extracted from the 1.6.0 version of VisualizationViewer
+ * 
+ * @author Tom Nelson
+ */
+public class DefaultVisualizationModel<V, E> implements VisualizationModel<V,E>, ChangeEventSupport {
+    
+    ChangeEventSupport changeSupport = new DefaultChangeEventSupport(this);
+
+    /**
+	 * manages the thread that applies the current layout algorithm
+	 */
+	protected Relaxer relaxer;
+	
+	/**
+	 * the layout algorithm currently in use
+	 */
+	protected Layout<V,E> layout;
+	
+	/**
+	 * listens for changes in the layout, forwards to the viewer
+	 *
+	 */
+    protected ChangeListener changeListener;
+    
+    /**
+     * 
+     * @param layout The Layout to apply, with its associated Graph
+     */
+	public DefaultVisualizationModel(Layout<V,E> layout) {
+        this(layout, null);
+	}
+    
+	/**
+	 * Create an instance with the specified layout and dimension.
+	 * @param layout the layout to use
+	 * @param d The preferred size of the View that will display this graph
+	 */
+	public DefaultVisualizationModel(Layout<V,E> layout, Dimension d) {
+        if(changeListener == null) {
+            changeListener = new ChangeListener() {
+                public void stateChanged(ChangeEvent e) {
+                    fireStateChanged();
+                }
+            };
+        }
+		setGraphLayout(layout, d);
+	}
+	
+	/**
+	 * Removes the current graph layout, and adds a new one.
+	 * @param layout   the new layout to use
+	 * @param viewSize the size of the View that will display this layout
+	 */
+	public void setGraphLayout(Layout<V,E> layout, Dimension viewSize) {
+		// remove listener from old layout
+	    if(this.layout != null && this.layout instanceof ChangeEventSupport) {
+	        ((ChangeEventSupport)this.layout).removeChangeListener(changeListener);
+        }
+	    // set to new layout
+	    if(layout instanceof ChangeEventSupport) {
+	    	this.layout = layout;
+	    } else {
+	    	this.layout = new ObservableCachingLayout<V,E>(layout);
+	    }
+		
+		((ChangeEventSupport)this.layout).addChangeListener(changeListener);
+
+        if(viewSize == null) {
+            viewSize = new Dimension(600,600);
+        }
+		Dimension layoutSize = layout.getSize();
+		// if the layout has NOT been initialized yet, initialize its size
+		// now to the size of the VisualizationViewer window
+		if(layoutSize == null) {
+		    layout.setSize(viewSize);
+        }
+        if(relaxer != null) {
+        	relaxer.stop();
+        	relaxer = null;
+        }
+        if(layout instanceof IterativeContext) {
+        	layout.initialize();
+            if(relaxer == null) {
+            	relaxer = new VisRunner((IterativeContext)this.layout);
+            	relaxer.prerelax();
+            	relaxer.relax();
+            }
+        }
+        fireStateChanged();
+	}
+
+	/**
+	 * set the graph Layout and if it is not already initialized, initialize
+	 * it to the default VisualizationViewer preferred size of 600x600
+	 */
+	public void setGraphLayout(Layout<V,E> layout) {
+	    setGraphLayout(layout, null);
+	}
+
+    /**
+	 * Returns the current graph layout.
+	 */
+	public Layout<V,E> getGraphLayout() {
+	        return layout;
+	}
+
+	/**
+	 * @return the relaxer
+	 */
+	public Relaxer getRelaxer() {
+		return relaxer;
+	}
+
+	/**
+	 * @param relaxer the relaxer to set
+	 */
+	public void setRelaxer(VisRunner relaxer) {
+		this.relaxer = relaxer;
+	}
+
+    /**
+     * Adds a <code>ChangeListener</code>.
+     * @param l the listener to be added
+     */
+    public void addChangeListener(ChangeListener l) {
+        changeSupport.addChangeListener(l);
+    }
+    
+    /**
+     * Removes a ChangeListener.
+     * @param l the listener to be removed
+     */
+    public void removeChangeListener(ChangeListener l) {
+        changeSupport.removeChangeListener(l);
+    }
+    
+    /**
+     * Returns an array of all the <code>ChangeListener</code>s added
+     * with addChangeListener().
+     *
+     * @return all of the <code>ChangeListener</code>s added or an empty
+     *         array if no listeners have been added
+     */
+    public ChangeListener[] getChangeListeners() {
+        return changeSupport.getChangeListeners();
+    }
+
+    /**
+     * Notifies all listeners that have registered interest for
+     * notification on this event type.  The event instance 
+     * is lazily created.
+     * The primary listeners will be views that need to be repainted
+     * because of changes in this model instance
+     * @see EventListenerList
+     */
+    public void fireStateChanged() {
+        changeSupport.fireStateChanged();
+    }   
+    
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/FourPassImageShaper.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/FourPassImageShaper.java
new file mode 100644
index 0000000..5b5f1c7
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/FourPassImageShaper.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jun 17, 2005
+ */
+
+package edu.uci.ics.jung.visualization;
+
+import java.awt.Shape;
+import java.awt.geom.Area;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.awt.image.BufferedImage;
+
+/**
+ * Provides Supplier methods that, given a BufferedImage, an Image,
+ * or the fileName of an image, will return a java.awt.Shape that
+ * is the contiguous traced outline of the opaque part of the image.
+ * This could be used to define an image for use in a Vertex, where
+ * the shape used for picking and edge-arrow placement follows the
+ * opaque part of an image that has a transparent background.
+ * The methods try to detect lines in order to minimize points
+ * in the path
+ * 
+ * @author Tom Nelson
+ *
+ * 
+ */
+public class FourPassImageShaper {
+    
+    public static Shape getShape(BufferedImage image) {
+        Area area = new Area(leftEdge(image));
+        area.intersect(new Area(bottomEdge(image)));
+        area.intersect(new Area(rightEdge(image)));
+        area.intersect(new Area(topEdge(image)));
+        return area;
+    }
+    /**
+     * Checks to see if point p is on a line that passes thru
+     * points p1 and p2. If p is on the line, extend the line
+     * segment so that it is from p1 to the location of p.
+     * If the point p is not on the line, update my shape
+     * with a line extending to the old p2 location, make
+     * the old p2 the new p1, and make p2 the old p
+     * @param p1
+     * @param p2
+     * @param p
+     * @param line
+     * @param path
+     * @return
+     */
+    private static Point2D detectLine(Point2D p1, Point2D p2, Point2D p, 
+            Line2D line, GeneralPath path) {
+    	
+        // check for line
+        // if p is on the line that extends thru p1 and p2
+    	if(line.ptLineDistSq(p) == 0) { // p is on the line p1,p2
+            // extend line so that p2 is at p
+            p2.setLocation(p);
+        } else { // its not on the current line
+        	// start a new line from p2 to p
+            p1.setLocation(p2);
+            p2.setLocation(p);
+            line.setLine(p1,p2);
+            // end the ongoing path line at the new p1 (the old p2)
+            path.lineTo((float)p1.getX(), (float)p1.getY());
+        }
+        return p2;
+    }
+    /**
+     * trace the left side of the image
+     * @param image
+     * @param path
+     * @return
+     */
+    private static Shape leftEdge(BufferedImage image) {
+        GeneralPath path = new GeneralPath();
+        Point2D p1 = null;
+        Point2D p2 = null;
+        Line2D line = new Line2D.Float();
+        Point2D p = new Point2D.Float();
+        int foundPointY = -1;
+        for(int i=0; i<image.getHeight(); i++) {
+            // go until we reach an opaque point, then stop
+            for(int j=0; j<image.getWidth(); j++) {
+                if((image.getRGB(j,i) & 0xff000000) != 0) {
+                    // this is a point I want
+                    p = new Point2D.Float(j,i);
+                    foundPointY = i;
+                    break;
+                }
+            }
+            if(foundPointY >= 0) {
+            	if(p2 == null) {
+            		// this is the first point found. project line to right edge
+            		p1 = new Point2D.Float(image.getWidth()-1, foundPointY);
+            		path.moveTo(p1.getX(), p1.getY());
+            		p2 = new Point2D.Float();
+            		p2.setLocation(p);
+            	} else {
+            		p2 = detectLine(p1, p2, p, line, path);
+            	}
+            }
+        }
+        path.lineTo(p.getX(), p.getY());
+        if(foundPointY >= 0) {
+        	path.lineTo(image.getWidth()-1, foundPointY);
+        }
+        path.closePath();
+        return path;
+    }
+    
+    /**
+     * trace the bottom of the image
+     * @param image
+     * @param path
+     * @param start
+     * @return
+     */
+    private static Shape bottomEdge(BufferedImage image) {
+        GeneralPath path = new GeneralPath();
+        Point2D p1 = null;
+        Point2D p2 = null;
+        Line2D line = new Line2D.Float();
+        Point2D p = new Point2D.Float();
+        int foundPointX = -1;
+        for(int i=0; i<image.getWidth(); i++) {
+            for(int j=image.getHeight()-1; j>=0; j--) {
+                if((image.getRGB(i,j) & 0xff000000) != 0) {
+                    // this is a point I want
+                    p.setLocation(i,j);
+                    foundPointX = i;
+                    break;
+                }
+            }
+            if(foundPointX >= 0) {
+            	if(p2 == null) {
+            		// this is the first point found. project line to top edge
+            		p1 = new Point2D.Float(foundPointX, 0);
+            		// path starts here
+            		path.moveTo(p1.getX(), p1.getY());
+            		p2 = new Point2D.Float();
+            		p2.setLocation(p);
+            	} else {
+            		p2 = detectLine(p1, p2, p, line, path);
+            	}
+            }
+        }
+        path.lineTo(p.getX(), p.getY());
+        if(foundPointX >= 0) {
+        	path.lineTo(foundPointX, 0);
+        }
+        path.closePath();
+        return path;
+    }
+    
+    /**
+     * trace the right side of the image
+     * @param image
+     * @param path
+     * @param start
+     * @return
+     */
+    private static Shape rightEdge(BufferedImage image) {
+        GeneralPath path = new GeneralPath();
+        Point2D p1 = null;
+        Point2D p2 = null;
+        Line2D line = new Line2D.Float();
+        Point2D p = new Point2D.Float();
+        int foundPointY = -1;
+        
+        for(int i=image.getHeight()-1; i>=0; i--) {
+            for(int j=image.getWidth()-1; j>=0; j--) {
+                if((image.getRGB(j,i) & 0xff000000) != 0) {
+                    // this is a point I want
+                    p.setLocation(j,i);
+                    foundPointY = i;
+                    break;
+                }
+            }
+            if(foundPointY >= 0) {
+            	if(p2 == null) {
+            		// this is the first point found. project line to top edge
+            		p1 = new Point2D.Float(0, foundPointY);
+            		// path starts here
+            		path.moveTo(p1.getX(), p1.getY());
+            		p2 = new Point2D.Float();
+            		p2.setLocation(p);
+            	} else {
+            		p2 = detectLine(p1, p2, p, line, path);
+            	}
+            }
+        }
+        path.lineTo(p.getX(), p.getY());
+        if(foundPointY >= 0) {
+        	path.lineTo(0, foundPointY);
+        }
+        path.closePath();
+        return path;
+    }
+    
+    /**
+     * trace the top of the image
+     * @param image
+     * @param path
+     * @param start
+     * @return
+     */
+    private static Shape topEdge(BufferedImage image) {
+        GeneralPath path = new GeneralPath();
+        Point2D p1 = null;
+        Point2D p2 = null;
+        Line2D line = new Line2D.Float();
+        Point2D p = new Point2D.Float();
+        int foundPointX = -1;
+        
+        for(int i=image.getWidth()-1; i>=0; i--) {
+            for(int j=0; j<image.getHeight(); j++) {
+                if((image.getRGB(i,j) & 0xff000000) != 0) {
+                    // this is a point I want
+                    p.setLocation(i,j);
+                    foundPointX = i;
+                    break;
+                }
+            }
+            if(foundPointX >= 0) {
+            	if(p2 == null) {
+            		// this is the first point found. project line to top edge
+            		p1 = new Point2D.Float(foundPointX, image.getHeight()-1);
+            		// path starts here
+            		path.moveTo(p1.getX(), p1.getY());
+            		p2 = new Point2D.Float();
+            		p2.setLocation(p);
+            	} else {
+            		p2 = detectLine(p1, p2, p, line, path);
+            	}
+            }
+        }
+        path.lineTo(p.getX(), p.getY());
+        if(foundPointX >= 0) {
+        	path.lineTo(foundPointX, image.getHeight()-1);
+        }
+        path.closePath();
+        return path;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/GraphZoomScrollPane.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/GraphZoomScrollPane.java
new file mode 100644
index 0000000..a06b71c
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/GraphZoomScrollPane.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Feb 2, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Set;
+
+import javax.swing.BoundedRangeModel;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JScrollBar;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.Intersector;
+
+
+
+/**
+ * GraphZoomScrollPane is a Container for the Graph's VisualizationViewer
+ * and includes custom horizontal and vertical scrollbars.
+ * GraphZoomScrollPane listens for changes in the scale and
+ * translation of the VisualizationViewer, and will update the
+ * scrollbar positions and sizes accordingly. Changes in the
+ * scrollbar positions will cause the corresponding change in
+ * the translation component (offset) of the VisualizationViewer.
+ * The scrollbars are modified so that they will allow panning
+ * of the graph when the scale has been changed (e.g. zoomed-in
+ * or zoomed-out).
+ * 
+ * The lower-right corner of this component is available to
+ * use as a small button or menu.
+ * 
+ * samples.graph.GraphZoomScrollPaneDemo shows the use of this component.
+ * 
+ * @author Tom Nelson 
+ *
+ * 
+ */
+ at SuppressWarnings("serial")
+public class GraphZoomScrollPane extends JPanel {
+    protected VisualizationViewer<?, ?> vv;
+    protected JScrollBar horizontalScrollBar;
+    protected JScrollBar verticalScrollBar;
+    protected JComponent corner;
+    protected boolean scrollBarsMayControlAdjusting = true;
+    protected JPanel south;
+    
+    /**
+     * Create an instance of the GraphZoomScrollPane to contain the
+     * VisualizationViewer
+     * @param vv the VisualizationViewer for which this instance is to be created
+     */
+    public GraphZoomScrollPane(VisualizationViewer<?, ?> vv) {
+        super(new BorderLayout());
+        this.vv = vv;
+        addComponentListener(new ResizeListener());        
+        Dimension d = vv.getGraphLayout().getSize();
+        verticalScrollBar = new JScrollBar(JScrollBar.VERTICAL, 0, d.height, 0, d.height);
+        horizontalScrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 0, d.width, 0, d.width);
+        verticalScrollBar.addAdjustmentListener(new VerticalAdjustmentListenerImpl());
+        horizontalScrollBar.addAdjustmentListener(new HorizontalAdjustmentListenerImpl());
+        verticalScrollBar.setUnitIncrement(20);
+        horizontalScrollBar.setUnitIncrement(20);
+        // respond to changes in the VisualizationViewer's transform
+        // and set the scroll bar parameters appropriately
+        vv.addChangeListener(
+                new ChangeListener(){
+            public void stateChanged(ChangeEvent evt) {
+                VisualizationViewer<?, ?> vv = 
+                    (VisualizationViewer<?, ?>)evt.getSource();
+                setScrollBars(vv);
+            }
+        });
+        add(vv);
+        add(verticalScrollBar, BorderLayout.EAST);
+        south = new JPanel(new BorderLayout());
+        south.add(horizontalScrollBar);
+        setCorner(new JPanel());
+        add(south, BorderLayout.SOUTH);
+    }
+    
+    /**
+     * listener for adjustment of the horizontal scroll bar.
+     * Sets the translation of the VisualizationViewer
+     */
+    class HorizontalAdjustmentListenerImpl implements AdjustmentListener {
+        int previous = 0;
+        public void adjustmentValueChanged(AdjustmentEvent e) {
+            int hval = e.getValue();
+            float dh = previous - hval;
+            previous = hval;
+            if(dh != 0 && scrollBarsMayControlAdjusting) {
+                // get the uniform scale of all transforms
+                float layoutScale = (float) vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale();
+                dh *= layoutScale;
+                AffineTransform at = AffineTransform.getTranslateInstance(dh, 0);
+                vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).preConcatenate(at);
+            }
+        }
+    }
+    
+    /**
+     * Listener for adjustment of the vertical scroll bar.
+     * Sets the translation of the VisualizationViewer
+     */
+    class VerticalAdjustmentListenerImpl implements AdjustmentListener {
+        int previous = 0;
+        public void adjustmentValueChanged(AdjustmentEvent e) {
+            JScrollBar sb = (JScrollBar)e.getSource();
+            BoundedRangeModel m = sb.getModel();
+            int vval = m.getValue();
+            float dv = previous - vval;
+            previous = vval;
+            if(dv != 0 && scrollBarsMayControlAdjusting) {
+            
+                // get the uniform scale of all transforms
+                float layoutScale = (float) vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale();
+                dv *= layoutScale;
+                AffineTransform at = AffineTransform.getTranslateInstance(0, dv);
+                vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).preConcatenate(at);
+            }
+        }
+    }
+    
+    /**
+     * use the supplied vv characteristics to set the position and
+     * dimensions of the scroll bars. Called in response to
+     * a ChangeEvent from the VisualizationViewer
+     * @param xform the transform of the VisualizationViewer
+     */
+    private void setScrollBars(VisualizationViewer<?, ?> vv) {
+        Dimension d = vv.getGraphLayout().getSize();
+        Rectangle2D vvBounds = vv.getBounds();
+        
+        // a rectangle representing the layout
+        Rectangle layoutRectangle = 
+            new Rectangle(0,0,d.width,d.height);
+            		//-d.width/2, -d.height/2, 2*d.width, 2*d.height);
+        
+        BidirectionalTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
+        BidirectionalTransformer layoutTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+
+        Point2D h0 = new Point2D.Double(vvBounds.getMinX(), vvBounds.getCenterY());
+        Point2D h1 = new Point2D.Double(vvBounds.getMaxX(), vvBounds.getCenterY());
+        Point2D v0 = new Point2D.Double(vvBounds.getCenterX(), vvBounds.getMinY());
+        Point2D v1 = new Point2D.Double(vvBounds.getCenterX(), vvBounds.getMaxY());
+        
+        h0 = viewTransformer.inverseTransform(h0);
+        h0 = layoutTransformer.inverseTransform(h0);
+        h1 = viewTransformer.inverseTransform(h1);
+        h1 = layoutTransformer.inverseTransform(h1);
+        v0 = viewTransformer.inverseTransform(v0);
+        v0 = layoutTransformer.inverseTransform(v0);
+        v1 = viewTransformer.inverseTransform(v1);
+        v1 = layoutTransformer.inverseTransform(v1);
+        
+        scrollBarsMayControlAdjusting = false;
+        setScrollBarValues(layoutRectangle, h0, h1, v0, v1);
+        scrollBarsMayControlAdjusting = true;
+    }
+    
+    protected void setScrollBarValues(Rectangle rectangle, 
+            Point2D h0, Point2D h1, 
+            Point2D v0, Point2D v1) {
+        boolean containsH0 = rectangle.contains(h0);
+        boolean containsH1 = rectangle.contains(h1);
+        boolean containsV0 = rectangle.contains(v0);
+        boolean containsV1 = rectangle.contains(v1);
+        
+        // horizontal scrollbar:
+        
+        Intersector intersector = new Intersector(rectangle, new Line2D.Double(h0, h1));
+        
+        int min = 0;
+        int ext;
+        int val = 0;
+        int max;
+        
+        Set<Point2D> points = intersector.getPoints();
+        Point2D first = null;
+        Point2D second = null;
+        
+        Point2D[] pointArray = (Point2D[])points.toArray(new Point2D[points.size()]);
+        if(pointArray.length > 1) {
+            first = pointArray[0];
+            second = pointArray[1];
+        } else if(pointArray.length > 0) {
+            first = second = pointArray[0];
+        }
+        
+        if(first != null && second != null) {
+            // correct direction of intersect points
+            if((h0.getX() - h1.getX()) * (first.getX() - second.getX()) < 0) {
+                // swap them
+                Point2D temp = first;
+                first = second;
+                second = temp;
+            }
+
+            if(containsH0 && containsH1) {
+                max = (int)first.distance(second);
+                val = (int)first.distance(h0);
+                ext = (int)h0.distance(h1);
+                
+            } else if(containsH0) {
+                max = (int)first.distance(second);
+                val = (int)first.distance(h0);
+                ext = (int)h0.distance(second);
+                
+            } else if(containsH1) {
+                max = (int) first.distance(second);
+                val = 0;
+                ext = (int) first.distance(h1);
+                
+            } else {
+                max = ext = rectangle.width;
+                val = min;
+            }
+            horizontalScrollBar.setValues(val, ext+1, min, max);
+        }
+        
+        // vertical scroll bar
+        min = val = 0;
+        
+        intersector.intersectLine(new Line2D.Double(v0, v1));
+        points = intersector.getPoints();
+        
+        pointArray = (Point2D[])points.toArray(new Point2D[points.size()]);
+        if(pointArray.length > 1) {
+            first = pointArray[0];
+            second = pointArray[1];
+        } else if(pointArray.length > 0) {
+            first = second = pointArray[0];
+        }
+        
+        if(first != null && second != null) {
+            
+            // arrange for direction
+            if((v0.getY() - v1.getY()) * (first.getY() - second.getY()) < 0) {
+                // swap them
+                Point2D temp = first;
+                first = second;
+                second = temp;
+            }
+            
+            if(containsV0 && containsV1) {
+                max = (int)first.distance(second);
+                val = (int)first.distance(v0);
+                ext = (int)v0.distance(v1);
+                
+            } else if(containsV0) {
+                max = (int)first.distance(second);
+                val = (int)first.distance(v0);
+                ext = (int)v0.distance(second);
+                
+            } else if(containsV1) {
+                max = (int) first.distance(second);
+                val = 0;
+                ext = (int) first.distance(v1);
+                
+            } else {
+                max = ext = rectangle.height;
+                val = min;
+            }
+            verticalScrollBar.setValues(val, ext+1, min, max);
+        }
+    }
+
+    /**
+     * Listener to adjust the scroll bar parameters when the window
+     * is resized
+     */
+	protected class ResizeListener extends ComponentAdapter {
+
+		public void componentHidden(ComponentEvent e) {
+		}
+
+		public void componentResized(ComponentEvent e) {
+		    setScrollBars(vv);
+		}	
+		public void componentShown(ComponentEvent e) {
+		}
+	}
+
+    /**
+     * @return Returns the corner component.
+     */
+    public JComponent getCorner() {
+        return corner;
+    }
+
+    /**
+     * @param corner The cornerButton to set.
+     */
+    public void setCorner(JComponent corner) {
+        this.corner = corner;
+        corner.setPreferredSize(new Dimension(verticalScrollBar.getPreferredSize().width,
+                horizontalScrollBar.getPreferredSize().height));
+        south.add(this.corner, BorderLayout.EAST);
+    }
+
+    public JScrollBar getHorizontalScrollBar() {
+        return horizontalScrollBar;
+    }
+
+    public JScrollBar getVerticalScrollBar() {
+        return verticalScrollBar;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/Layer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/Layer.java
new file mode 100644
index 0000000..708e980
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/Layer.java
@@ -0,0 +1,5 @@
+package edu.uci.ics.jung.visualization;
+
+public enum Layer {
+	LAYOUT, VIEW
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/LayeredIcon.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/LayeredIcon.java
new file mode 100644
index 0000000..c648022
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/LayeredIcon.java
@@ -0,0 +1,48 @@
+package edu.uci.ics.jung.visualization;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+
+/**
+ * An icon that is made up of a collection of Icons.
+ * They are rendered in layers starting with the first
+ * Icon added (from the constructor).
+ * 
+ * @author Tom Nelson
+ *
+ */
+ at SuppressWarnings("serial")
+public class LayeredIcon extends ImageIcon {
+
+	Set<Icon> iconSet = new LinkedHashSet<Icon>();
+
+	public LayeredIcon(Image image) {
+	    super(image);
+	}
+
+	public void paintIcon(Component c, Graphics g, int x, int y) {
+        super.paintIcon(c, g, x, y);
+        Dimension d = new Dimension(getIconWidth(), getIconHeight());
+		for (Icon icon : iconSet) {
+			Dimension id = new Dimension(icon.getIconWidth(), icon.getIconHeight());
+			int dx = (d.width - id.width)/2;
+			int dy = (d.height - id.height)/2;
+			icon.paintIcon(c, g, x+dx, y+dy);
+		}
+	}
+
+	public void add(Icon icon) {
+		iconSet.add(icon);
+	}
+
+	public boolean remove(Icon icon) {
+		return iconSet.remove(icon);
+	}
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/MultiLayerTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/MultiLayerTransformer.java
new file mode 100644
index 0000000..566f050
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/MultiLayerTransformer.java
@@ -0,0 +1,28 @@
+package edu.uci.ics.jung.visualization;
+
+import java.awt.Shape;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer;
+import edu.uci.ics.jung.visualization.util.ChangeEventSupport;
+
+public interface MultiLayerTransformer extends BidirectionalTransformer, ShapeTransformer, ChangeEventSupport {
+
+	
+	void setTransformer(Layer layer, MutableTransformer Function);
+
+	MutableTransformer getTransformer(Layer layer);
+
+	Point2D inverseTransform(Layer layer, Point2D p);
+
+	Point2D transform(Layer layer, Point2D p);
+
+	Shape transform(Layer layer, Shape shape);
+	
+	Shape inverseTransform(Layer layer, Shape shape);
+
+	void setToIdentity();
+
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/PivotingImageShaper.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/PivotingImageShaper.java
new file mode 100644
index 0000000..eac96b8
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/PivotingImageShaper.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jun 17, 2005
+ */
+
+package edu.uci.ics.jung.visualization;
+
+import java.awt.Shape;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.awt.image.BufferedImage;
+
+
+/**
+ * Provides Supplier methods that, given a BufferedImage, an Image,
+ * or the fileName of an image, will return a java.awt.Shape that
+ * is the contiguous traced outline of the opaque part of the image.
+ * This could be used to define an image for use in a Vertex, where
+ * the shape used for picking and edge-arrow placement follows the
+ * opaque part of an image that has a transparent background.
+ * The methods try to detect lines in order to minimize points
+ * in the path
+ * 
+ * @author Tom Nelson 
+ *
+ * 
+ */
+public class PivotingImageShaper {
+    
+    /**
+     * the number of pixels to skip while sampling the
+     * images edges
+     */
+    static int sample = 1;
+    /**
+     * the first x coordinate of the shape. Used to discern
+     * when we are done 
+     */
+    static int firstx = 0;
+    
+    
+    /**
+     * Given an image, possibly with a transparent background, return
+     * the Shape of the opaque part of the image
+     * @param image the image whose shape is being returned
+     * @return the Shape
+     */
+    public static Shape getShape(BufferedImage image) {
+        firstx = 0;
+        return leftEdge(image, new GeneralPath());
+    }
+    
+    private static Point2D detectLine(Point2D p1, Point2D p2, Point2D p, 
+            Line2D line, GeneralPath path) {
+        if(p2 == null) {
+            p2 = p;
+            line.setLine(p1,p2);
+        }
+        // check for line
+        else if(line.ptLineDistSq(p) < 1) { // its on the line
+            // make it p2
+            p2.setLocation(p);
+        } else { // its not on the current line
+            p1.setLocation(p2);
+            p2.setLocation(p);
+            line.setLine(p1,p2);
+            path.lineTo((float)p1.getX(), (float)p1.getY());
+        }
+        return p2;
+    }
+    /**
+     * trace the left side of the image
+     * @param image
+     * @param path
+     * @return
+     */
+    private static Shape leftEdge(BufferedImage image, GeneralPath path) {
+        int lastj = 0;
+        Point2D p1 = null;
+        Point2D p2 = null;
+        Line2D line = new Line2D.Float();
+        for(int i=0; i<image.getHeight(); i+=sample) {
+            boolean aPointExistsOnThisLine = false;
+            // go until we reach an opaque point, then stop
+            for(int j=0; j<image.getWidth(); j+=sample) {
+                if((image.getRGB(j,i) & 0xff000000) != 0) {
+                    // this is a point I want
+                    Point2D p = new Point2D.Float(j,i);
+                    aPointExistsOnThisLine = true;
+                    if(path.getCurrentPoint() != null) {
+                        // this is a continuation of a path
+                        p2 = detectLine(p1,p2,p,line,path);
+                    } else {
+                        // this is the first point in the path
+                        path.moveTo(j,i);
+                        firstx = j;
+                        p1 = p;
+                    }
+                    lastj = j;
+                    break;
+                }
+            }
+            if(aPointExistsOnThisLine == false) {
+                break;
+            }
+        }
+        return bottomEdge(image, path, lastj);
+    }
+    
+    /**
+     * trace the bottom of the image
+     * @param image
+     * @param path
+     * @param start
+     * @return
+     */
+    private static Shape bottomEdge(BufferedImage image, GeneralPath path, int start) {
+        int lastj = 0;
+        Point2D p1 = path.getCurrentPoint();
+        Point2D p2 = null;
+        Line2D line = new Line2D.Float();
+        for(int i=start; i<image.getWidth(); i+=sample) {
+            boolean aPointExistsOnThisLine = false;
+            for(int j=image.getHeight()-1; j>=0; j-=sample) {
+                if((image.getRGB(i,j) & 0xff000000) != 0) {
+                    // this is a point I want
+                    Point2D p = new Point2D.Float(i,j);
+                    aPointExistsOnThisLine = true;
+                    p2 = detectLine(p1,p2,p,line,path);
+                    lastj = j;
+                    break;
+                }
+            }
+            if(aPointExistsOnThisLine == false) {
+                break;
+            }
+        }
+        return rightEdge(image, path, lastj);
+    }
+    
+    /**
+     * trace the right side of the image
+     * @param image
+     * @param path
+     * @param start
+     * @return
+     */
+    private static Shape rightEdge(BufferedImage image, GeneralPath path, int start) {
+        int lastj = 0;
+        Point2D p1 = path.getCurrentPoint();
+        Point2D p2 = null;
+        Line2D line = new Line2D.Float();
+        for(int i=start; i>=0; i-=sample) {
+            boolean aPointExistsOnThisLine = false;
+
+            for(int j=image.getWidth()-1; j>=0; j-=sample) {
+                if((image.getRGB(j,i) & 0xff000000) != 0) {
+                    // this is a point I want
+                    Point2D p = new Point2D.Float(j,i);
+                    aPointExistsOnThisLine = true;
+                    p2 = detectLine(p1,p2,p,line,path);
+                    lastj=j;
+                    break;
+                }
+            }
+            if(aPointExistsOnThisLine == false) {
+                break;
+            }
+        }
+        return topEdge(image, path, lastj);
+    }
+    
+    /**
+     * trace the top of the image
+     * @param image
+     * @param path
+     * @param start
+     * @return
+     */
+    private static Shape topEdge(BufferedImage image, GeneralPath path, int start) {
+        Point2D p1 = path.getCurrentPoint();
+        Point2D p2 = null;
+        Line2D line = new Line2D.Float();
+        for(int i=start; i>=firstx; i-=sample) {
+            boolean aPointExistsOnThisLine = false;
+            for(int j=0; j<image.getHeight(); j+=sample) {
+                if((image.getRGB(i,j) & 0xff000000) != 0) {
+                    // this is a point I want
+                    Point2D p = new Point2D.Float(i,j);
+                    aPointExistsOnThisLine = true;
+                    p2 = detectLine(p1,p2,p,line,path);
+                    break;
+                }
+            }
+            if(aPointExistsOnThisLine == false) {
+                break;
+            }
+        }
+        path.closePath();
+        return path;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/PluggableRenderContext.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/PluggableRenderContext.java
new file mode 100644
index 0000000..0a6fc66
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/PluggableRenderContext.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.visualization;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.Ellipse2D;
+
+import javax.swing.CellRendererPane;
+import javax.swing.Icon;
+import javax.swing.JComponent;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.graph.util.DefaultParallelEdgeIndexFunction;
+import edu.uci.ics.jung.graph.util.EdgeIndexFunction;
+import edu.uci.ics.jung.graph.util.IncidentEdgeIndexFunction;
+import edu.uci.ics.jung.visualization.decorators.ConstantDirectionalEdgeValueTransformer;
+import edu.uci.ics.jung.visualization.decorators.DirectionalEdgeArrowTransformer;
+import edu.uci.ics.jung.visualization.decorators.EdgeShape;
+import edu.uci.ics.jung.visualization.decorators.ParallelEdgeShapeTransformer;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer;
+import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer;
+import edu.uci.ics.jung.visualization.renderers.EdgeLabelRenderer;
+import edu.uci.ics.jung.visualization.renderers.VertexLabelRenderer;
+import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
+
+
+public class PluggableRenderContext<V, E> implements RenderContext<V, E> {
+    
+	protected float arrowPlacementTolerance = 1;
+    protected Predicate<Context<Graph<V,E>,V>> vertexIncludePredicate = Predicates.alwaysTrue();
+    protected Function<? super V,Stroke> vertexStrokeTransformer = 
+    	Functions.<Stroke>constant(new BasicStroke(1.0f));
+    
+    protected Function<? super V,Shape> vertexShapeTransformer = 
+        		Functions.<Shape>constant(
+        		new Ellipse2D.Float(-10,-10,20,20));
+
+    protected Function<? super V,String> vertexLabelTransformer = Functions.constant(null);
+    protected Function<? super V,Icon> vertexIconTransformer;
+    protected Function<? super V,Font> vertexFontTransformer = 
+        Functions.constant(new Font("Helvetica", Font.PLAIN, 12));
+    
+    protected Function<? super V,Paint> vertexDrawPaintTransformer = 
+    	Functions.<Paint>constant(Color.BLACK);
+    protected Function<? super V,Paint> vertexFillPaintTransformer = 
+    	Functions.<Paint>constant(Color.RED);
+    
+    protected Function<? super E,String> edgeLabelTransformer = 
+    	Functions.constant(null);
+    protected Function<? super E,Stroke> edgeStrokeTransformer = 
+    	Functions.<Stroke>constant(new BasicStroke(1.0f));
+    protected Function<? super E,Stroke> edgeArrowStrokeTransformer = 
+    	Functions.<Stroke>constant(new BasicStroke(1.0f));
+    
+    protected Function<? super Context<Graph<V,E>,E>,Shape> edgeArrowTransformer = 
+        new DirectionalEdgeArrowTransformer<V,E>(10, 8, 4);
+    
+    protected Predicate<Context<Graph<V,E>,E>> edgeArrowPredicate = new DirectedEdgeArrowPredicate<V,E>();
+    protected Predicate<Context<Graph<V,E>,E>> edgeIncludePredicate = Predicates.alwaysTrue();
+    protected Function<? super E,Font> edgeFontTransformer =
+        Functions.constant(new Font("Helvetica", Font.PLAIN, 12));
+    protected Function<? super Context<Graph<V,E>,E>,Number> edgeLabelClosenessTransformer = 
+        new ConstantDirectionalEdgeValueTransformer<V,E>(0.5, 0.65);
+    protected Function<? super E, Shape> edgeShapeTransformer;
+    protected Function<? super E,Paint> edgeFillPaintTransformer =
+        Functions.constant(null);
+    protected Function<? super E,Paint> edgeDrawPaintTransformer =
+        Functions.<Paint>constant(Color.black);
+    protected Function<? super E,Paint> arrowFillPaintTransformer =
+        Functions.<Paint>constant(Color.black);
+    protected Function<? super E,Paint> arrowDrawPaintTransformer =
+        Functions.<Paint>constant(Color.black);
+    
+    protected EdgeIndexFunction<V,E> parallelEdgeIndexFunction = 
+        DefaultParallelEdgeIndexFunction.<V,E>getInstance();
+    
+    protected EdgeIndexFunction<V,E> incidentEdgeIndexFunction = 
+        IncidentEdgeIndexFunction.<V,E>getInstance();
+    
+    protected MultiLayerTransformer multiLayerTransformer = new BasicTransformer();
+    
+	/**
+	 * pluggable support for picking graph elements by
+	 * finding them based on their coordinates.
+	 */
+	protected GraphElementAccessor<V, E> pickSupport;
+
+    
+    protected int labelOffset = LABEL_OFFSET;
+    
+    /**
+     * the JComponent that this Renderer will display the graph on
+     */
+    protected JComponent screenDevice;
+    
+    protected PickedState<V> pickedVertexState;
+    protected PickedState<E> pickedEdgeState;
+    
+    /**
+     * The CellRendererPane is used here just as it is in JTree
+     * and JTable, to allow a pluggable JLabel-based renderer for
+     * Vertex and Edge label strings and icons.
+     */
+    protected CellRendererPane rendererPane = new CellRendererPane();
+    
+    /**
+     * A default GraphLabelRenderer - picked Vertex labels are
+     * blue, picked edge labels are cyan
+     */
+    protected VertexLabelRenderer vertexLabelRenderer = 
+        new DefaultVertexLabelRenderer(Color.blue);
+    
+    protected EdgeLabelRenderer edgeLabelRenderer = new DefaultEdgeLabelRenderer(Color.cyan);
+    
+    protected GraphicsDecorator graphicsContext;
+    
+    private EdgeShape<V, E> edgeShape;
+    
+    PluggableRenderContext(Graph<V, E> graph) {
+        this.edgeShape = new EdgeShape<V, E>(graph);
+    	this.edgeShapeTransformer = edgeShape.new QuadCurve();    	
+    }
+
+	/**
+	 * @return the vertexShapeTransformer
+	 */
+	public Function<? super V, Shape> getVertexShapeTransformer() {
+		return vertexShapeTransformer;
+	}
+
+	/**
+	 * @param vertexShapeTransformer the vertexShapeTransformer to set
+	 */
+	public void setVertexShapeTransformer(
+			Function<? super V, Shape> vertexShapeTransformer) {
+		this.vertexShapeTransformer = vertexShapeTransformer;
+	}
+
+	/**
+	 * @return the vertexStrokeTransformer
+	 */
+	public Function<? super V, Stroke> getVertexStrokeTransformer() {
+		return vertexStrokeTransformer;
+	}
+
+	/**
+	 * @param vertexStrokeTransformer the vertexStrokeTransformer to set
+	 */
+	public void setVertexStrokeTransformer(
+			Function<? super V, Stroke> vertexStrokeTransformer) {
+		this.vertexStrokeTransformer = vertexStrokeTransformer;
+	}
+
+	public static float[] getDashing() {
+        return dashing;
+    }
+
+    public static float[] getDotting() {
+        return dotting;
+    }
+
+    public float getArrowPlacementTolerance() {
+        return arrowPlacementTolerance;
+    }
+
+    public void setArrowPlacementTolerance(float arrow_placement_tolerance) {
+        this.arrowPlacementTolerance = arrow_placement_tolerance;
+    }
+
+    public Function<? super Context<Graph<V,E>,E>,Shape> getEdgeArrowTransformer() {
+        return edgeArrowTransformer;
+    }
+
+    public void setEdgeArrowTransformer(Function<? super Context<Graph<V,E>,E>,Shape> edgeArrowTransformer) {
+        this.edgeArrowTransformer = edgeArrowTransformer;
+    }
+
+    public Predicate<Context<Graph<V,E>,E>> getEdgeArrowPredicate() {
+        return edgeArrowPredicate;
+    }
+
+    public void setEdgeArrowPredicate(Predicate<Context<Graph<V,E>,E>> edgeArrowPredicate) {
+        this.edgeArrowPredicate = edgeArrowPredicate;
+    }
+
+    public Function<? super E,Font> getEdgeFontTransformer() {
+        return edgeFontTransformer;
+    }
+
+    public void setEdgeFontTransformer(Function<? super E,Font> edgeFontTransformer) {
+        this.edgeFontTransformer = edgeFontTransformer;
+    }
+
+    public Predicate<Context<Graph<V,E>,E>> getEdgeIncludePredicate() {
+        return edgeIncludePredicate;
+    }
+
+    public void setEdgeIncludePredicate(Predicate<Context<Graph<V,E>,E>> edgeIncludePredicate) {
+        this.edgeIncludePredicate = edgeIncludePredicate;
+    }
+
+    public Function<? super Context<Graph<V,E>,E>,Number> getEdgeLabelClosenessTransformer() {
+        return edgeLabelClosenessTransformer;
+    }
+
+    public void setEdgeLabelClosenessTransformer(
+    		Function<? super Context<Graph<V,E>,E>,Number> edgeLabelClosenessTransformer) {
+        this.edgeLabelClosenessTransformer = edgeLabelClosenessTransformer;
+    }
+
+    public EdgeLabelRenderer getEdgeLabelRenderer() {
+        return edgeLabelRenderer;
+    }
+
+    public void setEdgeLabelRenderer(EdgeLabelRenderer edgeLabelRenderer) {
+        this.edgeLabelRenderer = edgeLabelRenderer;
+    }
+
+    public Function<? super E,Paint> getEdgeFillPaintTransformer() {
+        return edgeFillPaintTransformer;
+    }
+
+    public void setEdgeDrawPaintTransformer(Function<? super E,Paint> edgeDrawPaintTransformer) {
+        this.edgeDrawPaintTransformer = edgeDrawPaintTransformer;
+    }
+
+    public Function<? super E,Paint> getEdgeDrawPaintTransformer() {
+        return edgeDrawPaintTransformer;
+    }
+
+    public void setEdgeFillPaintTransformer(Function<? super E,Paint> edgeFillPaintTransformer) {
+        this.edgeFillPaintTransformer = edgeFillPaintTransformer;
+    }
+
+    public Function<? super E, Shape> getEdgeShapeTransformer() {
+        return edgeShapeTransformer;
+    }
+
+    public void setEdgeShapeTransformer(Function<? super E, Shape> edgeShapeTransformer) {
+        this.edgeShapeTransformer = edgeShapeTransformer;
+        if (edgeShapeTransformer instanceof ParallelEdgeShapeTransformer) {
+        	@SuppressWarnings("unchecked")
+			ParallelEdgeShapeTransformer<V, E> transformer =
+        			(ParallelEdgeShapeTransformer<V, E>)edgeShapeTransformer;
+        	if (transformer instanceof EdgeShape.Orthogonal) {
+        		transformer.setEdgeIndexFunction(this.incidentEdgeIndexFunction);
+        	} else {
+        		transformer.setEdgeIndexFunction(this.parallelEdgeIndexFunction);
+        	}
+        }
+    }
+
+    public Function<? super E,String> getEdgeLabelTransformer() {
+        return edgeLabelTransformer;
+    }
+
+    public void setEdgeLabelTransformer(Function<? super E,String> edgeLabelTransformer) {
+        this.edgeLabelTransformer = edgeLabelTransformer;
+    }
+
+    public Function<? super E,Stroke> getEdgeStrokeTransformer() {
+        return edgeStrokeTransformer;
+    }
+
+    public void setEdgeStrokeTransformer(Function<? super E,Stroke> edgeStrokeTransformer) {
+        this.edgeStrokeTransformer = edgeStrokeTransformer;
+    }
+
+    public Function<? super E,Stroke> getEdgeArrowStrokeTransformer() {
+        return edgeArrowStrokeTransformer;
+    }
+
+    public void setEdgeArrowStrokeTransformer(Function<? super E,Stroke> edgeArrowStrokeTransformer) {
+        this.edgeArrowStrokeTransformer = edgeArrowStrokeTransformer;
+    }
+
+    public GraphicsDecorator getGraphicsContext() {
+        return graphicsContext;
+    }
+
+    public void setGraphicsContext(GraphicsDecorator graphicsContext) {
+        this.graphicsContext = graphicsContext;
+    }
+
+    public int getLabelOffset() {
+        return labelOffset;
+    }
+
+    public void setLabelOffset(int labelOffset) {
+        this.labelOffset = labelOffset;
+    }
+
+    public EdgeIndexFunction<V, E> getParallelEdgeIndexFunction() {
+        return parallelEdgeIndexFunction;
+    }
+
+    public void setParallelEdgeIndexFunction(
+            EdgeIndexFunction<V, E> parallelEdgeIndexFunction) {
+        this.parallelEdgeIndexFunction = parallelEdgeIndexFunction;
+        // reset the edge shape Function, as the parallel edge index function
+        // is used by it
+        this.setEdgeShapeTransformer(getEdgeShapeTransformer());
+    }
+
+    public PickedState<E> getPickedEdgeState() {
+        return pickedEdgeState;
+    }
+
+    public void setPickedEdgeState(PickedState<E> pickedEdgeState) {
+        this.pickedEdgeState = pickedEdgeState;
+    }
+
+    public PickedState<V> getPickedVertexState() {
+        return pickedVertexState;
+    }
+
+    public void setPickedVertexState(PickedState<V> pickedVertexState) {
+        this.pickedVertexState = pickedVertexState;
+    }
+
+    public CellRendererPane getRendererPane() {
+        return rendererPane;
+    }
+
+    public void setRendererPane(CellRendererPane rendererPane) {
+        this.rendererPane = rendererPane;
+    }
+
+    public JComponent getScreenDevice() {
+        return screenDevice;
+    }
+
+    public void setScreenDevice(JComponent screenDevice) {
+        this.screenDevice = screenDevice;
+        screenDevice.add(rendererPane);
+    }
+
+    public Function<? super V,Font> getVertexFontTransformer() {
+        return vertexFontTransformer;
+    }
+
+    public void setVertexFontTransformer(Function<? super V,Font> vertexFontTransformer) {
+        this.vertexFontTransformer = vertexFontTransformer;
+    }
+
+    public Function<? super V,Icon> getVertexIconTransformer() {
+        return vertexIconTransformer;
+    }
+
+    public void setVertexIconTransformer(Function<? super V,Icon> vertexIconTransformer) {
+        this.vertexIconTransformer = vertexIconTransformer;
+    }
+
+    public Predicate<Context<Graph<V,E>,V>> getVertexIncludePredicate() {
+        return vertexIncludePredicate;
+    }
+
+    public void setVertexIncludePredicate(Predicate<Context<Graph<V,E>,V>> vertexIncludePredicate) {
+        this.vertexIncludePredicate = vertexIncludePredicate;
+    }
+
+    public VertexLabelRenderer getVertexLabelRenderer() {
+        return vertexLabelRenderer;
+    }
+
+    public void setVertexLabelRenderer(VertexLabelRenderer vertexLabelRenderer) {
+        this.vertexLabelRenderer = vertexLabelRenderer;
+    }
+
+    public Function<? super V,Paint> getVertexFillPaintTransformer() {
+        return vertexFillPaintTransformer;
+    }
+
+    public void setVertexFillPaintTransformer(Function<? super V,Paint> vertexFillPaintTransformer) {
+        this.vertexFillPaintTransformer = vertexFillPaintTransformer;
+    }
+
+    public Function<? super V,Paint> getVertexDrawPaintTransformer() {
+        return vertexDrawPaintTransformer;
+    }
+
+    public void setVertexDrawPaintTransformer(Function<? super V,Paint> vertexDrawPaintTransformer) {
+        this.vertexDrawPaintTransformer = vertexDrawPaintTransformer;
+    }
+
+    public Function<? super V,String> getVertexLabelTransformer() {
+        return vertexLabelTransformer;
+    }
+
+    public void setVertexLabelTransformer(Function<? super V,String> vertexLabelTransformer) {
+        this.vertexLabelTransformer = vertexLabelTransformer;
+    }
+
+	public GraphElementAccessor<V, E> getPickSupport() {
+		return pickSupport;
+	}
+
+	public void setPickSupport(GraphElementAccessor<V, E> pickSupport) {
+		this.pickSupport = pickSupport;
+	}
+	
+	public MultiLayerTransformer getMultiLayerTransformer() {
+		return multiLayerTransformer;
+	}
+
+	public void setMultiLayerTransformer(MultiLayerTransformer basicTransformer) {
+		this.multiLayerTransformer = basicTransformer;
+	}
+
+	public Function<? super E, Paint> getArrowDrawPaintTransformer() {
+		return arrowDrawPaintTransformer;
+	}
+
+	public Function<? super E, Paint> getArrowFillPaintTransformer() {
+		return arrowFillPaintTransformer;
+	}
+
+	public void setArrowDrawPaintTransformer(Function<? super E, Paint> arrowDrawPaintTransformer) {
+		this.arrowDrawPaintTransformer = arrowDrawPaintTransformer;
+		
+	}
+
+	public void setArrowFillPaintTransformer(Function<? super E, Paint> arrowFillPaintTransformer) {
+		this.arrowFillPaintTransformer = arrowFillPaintTransformer;
+		
+	}
+}
+
+
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/RenderContext.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/RenderContext.java
new file mode 100644
index 0000000..ead7d64
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/RenderContext.java
@@ -0,0 +1,210 @@
+package edu.uci.ics.jung.visualization;
+
+import java.awt.BasicStroke;
+import java.awt.Font;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.Stroke;
+
+import javax.swing.CellRendererPane;
+import javax.swing.Icon;
+import javax.swing.JComponent;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.graph.util.EdgeIndexFunction;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.renderers.EdgeLabelRenderer;
+import edu.uci.ics.jung.visualization.renderers.VertexLabelRenderer;
+import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
+
+public interface RenderContext<V, E> {
+
+    float[] dotting = {1.0f, 3.0f};
+    float[] dashing = {5.0f};
+
+    /**
+     * A stroke for a dotted line: 1 pixel width, round caps, round joins, and an 
+     * array of {1.0f, 3.0f}.
+     */
+    Stroke DOTTED = new BasicStroke(1.0f,
+            BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f, dotting, 0f);
+
+    /**
+     * A stroke for a dashed line: 1 pixel width, square caps, beveled joins, and an
+     * array of {5.0f}.
+     */
+    Stroke DASHED = new BasicStroke(1.0f,
+            BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL, 1.0f, dashing, 0f);
+
+    /**
+     * Specifies the offset for the edge labels.
+     */
+    int LABEL_OFFSET = 10;
+
+    int getLabelOffset();
+    
+    void setLabelOffset(int labelOffset);
+    
+    float getArrowPlacementTolerance();
+
+    void setArrowPlacementTolerance(float arrow_placement_tolerance);
+
+    Function<? super Context<Graph<V,E>,E>,Shape> getEdgeArrowTransformer();
+
+    void setEdgeArrowTransformer(Function<? super Context<Graph<V,E>,E>,Shape> edgeArrowTransformer);
+
+    Predicate<Context<Graph<V,E>,E>> getEdgeArrowPredicate() ;
+
+    void setEdgeArrowPredicate(Predicate<Context<Graph<V,E>,E>> edgeArrowPredicate);
+
+    Function<? super E,Font> getEdgeFontTransformer();
+
+    void setEdgeFontTransformer(Function<? super E,Font> edgeFontTransformer);
+
+    Predicate<Context<Graph<V,E>,E>> getEdgeIncludePredicate();
+
+    void setEdgeIncludePredicate(Predicate<Context<Graph<V,E>,E>> edgeIncludePredicate);
+
+    Function<? super Context<Graph<V,E>,E>,Number> getEdgeLabelClosenessTransformer();
+
+    void setEdgeLabelClosenessTransformer(
+    		Function<? super Context<Graph<V,E>,E>,Number> edgeLabelClosenessTransformer);
+
+    EdgeLabelRenderer getEdgeLabelRenderer();
+
+    void setEdgeLabelRenderer(EdgeLabelRenderer edgeLabelRenderer);
+
+    Function<? super E,Paint> getEdgeFillPaintTransformer();
+
+    void setEdgeFillPaintTransformer(Function<? super E,Paint> edgePaintTransformer);
+
+    Function<? super E,Paint> getEdgeDrawPaintTransformer();
+
+    void setEdgeDrawPaintTransformer(Function<? super E,Paint> edgeDrawPaintTransformer);
+
+    Function<? super E,Paint> getArrowDrawPaintTransformer();
+
+    void setArrowDrawPaintTransformer(Function<? super E,Paint> arrowDrawPaintTransformer);
+
+    Function<? super E,Paint> getArrowFillPaintTransformer();
+
+    void setArrowFillPaintTransformer(Function<? super E,Paint> arrowFillPaintTransformer);
+
+    Function<? super E, Shape> getEdgeShapeTransformer();
+
+    void setEdgeShapeTransformer(Function<? super E, Shape> edgeShapeTransformer);
+
+    Function<? super E,String> getEdgeLabelTransformer();
+
+    void setEdgeLabelTransformer(Function<? super E,String> edgeStringer);
+
+    Function<? super E,Stroke> getEdgeStrokeTransformer();
+
+    void setEdgeStrokeTransformer(Function<? super E,Stroke> edgeStrokeTransformer);
+    
+    Function<? super E,Stroke> getEdgeArrowStrokeTransformer();
+
+    void setEdgeArrowStrokeTransformer(Function<? super E,Stroke> edgeArrowStrokeTransformer);
+    
+    GraphicsDecorator getGraphicsContext();
+    
+    void setGraphicsContext(GraphicsDecorator graphicsContext);
+
+    EdgeIndexFunction<V, E> getParallelEdgeIndexFunction();
+
+    void setParallelEdgeIndexFunction(
+            EdgeIndexFunction<V, E> parallelEdgeIndexFunction);
+
+    PickedState<E> getPickedEdgeState();
+
+    void setPickedEdgeState(PickedState<E> pickedEdgeState);
+
+    PickedState<V> getPickedVertexState();
+
+    void setPickedVertexState(PickedState<V> pickedVertexState);
+
+    CellRendererPane getRendererPane();
+
+    void setRendererPane(CellRendererPane rendererPane);
+
+    JComponent getScreenDevice();
+
+    void setScreenDevice(JComponent screenDevice);
+
+    Function<? super V,Font> getVertexFontTransformer();
+
+    void setVertexFontTransformer(Function<? super V,Font> vertexFontTransformer);
+
+    Function<? super V,Icon> getVertexIconTransformer();
+
+    void setVertexIconTransformer(Function<? super V,Icon> vertexIconTransformer);
+
+    Predicate<Context<Graph<V,E>,V>> getVertexIncludePredicate();
+
+    void setVertexIncludePredicate(Predicate<Context<Graph<V,E>,V>> vertexIncludePredicate);
+
+    VertexLabelRenderer getVertexLabelRenderer();
+
+    void setVertexLabelRenderer(VertexLabelRenderer vertexLabelRenderer);
+
+    Function<? super V,Paint> getVertexFillPaintTransformer();
+
+    void setVertexFillPaintTransformer(Function<? super V,Paint> vertexFillPaintTransformer);
+
+    Function<? super V,Paint> getVertexDrawPaintTransformer();
+
+    void setVertexDrawPaintTransformer(Function<? super V,Paint> vertexDrawPaintTransformer);
+
+    Function<? super V,Shape> getVertexShapeTransformer();
+
+    void setVertexShapeTransformer(Function<? super V,Shape> vertexShapeTransformer);
+
+    Function<? super V,String> getVertexLabelTransformer();
+
+    void setVertexLabelTransformer(Function<? super V,String> vertexStringer);
+
+    Function<? super V,Stroke> getVertexStrokeTransformer();
+
+    void setVertexStrokeTransformer(Function<? super V,Stroke> vertexStrokeTransformer);
+
+    class DirectedEdgeArrowPredicate<V,E> 
+    	implements Predicate<Context<Graph<V,E>,E>> {
+
+        public boolean apply(Context<Graph<V,E>,E> c) {
+            return c.graph.getEdgeType(c.element) == EdgeType.DIRECTED;
+        }
+        
+    }
+    
+    class UndirectedEdgeArrowPredicate<V,E> 
+    	implements Predicate<Context<Graph<V,E>,E>> {
+    	//extends AbstractGraphPredicate<V,E> {
+
+        public boolean apply(Context<Graph<V,E>,E> c) {
+            return c.graph.getEdgeType(c.element) == EdgeType.UNDIRECTED;
+        }
+        
+    }
+    
+    MultiLayerTransformer getMultiLayerTransformer();
+    
+    void setMultiLayerTransformer(MultiLayerTransformer basicTransformer);
+    
+	/**
+	 * @return the pickSupport
+	 */
+	GraphElementAccessor<V, E> getPickSupport();
+
+	/**
+	 * @param pickSupport the pickSupport to set
+	 */
+	void setPickSupport(GraphElementAccessor<V, E> pickSupport);
+	
+
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/VisualizationImageServer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/VisualizationImageServer.java
new file mode 100644
index 0000000..0c90e54
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/VisualizationImageServer.java
@@ -0,0 +1,71 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.visualization;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.RenderingHints;
+import java.awt.geom.Point2D;
+import java.awt.image.BufferedImage;
+import java.util.HashMap;
+import java.util.Map;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+
+/**
+ * A class that could be used on the server side of a thin-client application. It creates the jung
+ * visualization, then produces an image of it.
+ * @author tom
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+ at SuppressWarnings("serial")
+public class VisualizationImageServer<V,E> extends BasicVisualizationServer<V,E> {
+
+    Map<RenderingHints.Key, Object> renderingHints = new HashMap<RenderingHints.Key, Object>();
+    
+    /**
+     * Creates a new instance with the specified layout and preferred size.
+     * 
+     * @param layout the Layout instance; provides the vertex locations
+     * @param preferredSize the preferred size of the image
+     */
+    public VisualizationImageServer(Layout<V,E> layout, Dimension preferredSize) {
+        super(layout, preferredSize);
+        setSize(preferredSize);
+        renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        addNotify();
+    }
+    
+    public Image getImage(Point2D center, Dimension d) 
+    {
+        int width = getWidth();
+        int height = getHeight();
+        
+        float scalex = (float)width/d.width;
+        float scaley = (float)height/d.height;
+        try 
+        {
+            renderContext.getMultiLayerTransformer().getTransformer(Layer.VIEW).scale(scalex, scaley, center);
+    
+            BufferedImage bi = new BufferedImage(width, height,
+                    BufferedImage.TYPE_INT_RGB);
+            Graphics2D graphics = bi.createGraphics();
+            graphics.setRenderingHints(renderingHints);
+            paint(graphics);
+            graphics.dispose();
+            return bi;
+        } finally {
+        	renderContext.getMultiLayerTransformer().getTransformer(Layer.VIEW).setToIdentity();
+        }
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/VisualizationModel.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/VisualizationModel.java
new file mode 100644
index 0000000..662fdbb
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/VisualizationModel.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on May 4, 2005
+ */
+
+package edu.uci.ics.jung.visualization;
+
+import java.awt.Dimension;
+
+import javax.swing.event.ChangeListener;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.util.Relaxer;
+import edu.uci.ics.jung.visualization.util.ChangeEventSupport;
+
+/**
+ * Interface for the state holding model of the VisualizationViewer.
+ * Refactored and extracted from the 1.6.0 version of VisualizationViewer
+ * 
+ * @author Tom Nelson 
+ */
+public interface VisualizationModel<V, E> extends ChangeEventSupport {
+
+	Relaxer getRelaxer();
+
+	/**
+     * set the graph Layout
+     * @param layout the layout to use
+     */
+    void setGraphLayout(Layout<V,E> layout);
+    
+    /**
+     * Sets the graph Layout and initialize the Layout size to
+     * the passed dimensions. The passed Dimension will often be
+     * the size of the View that will display the graph.
+     * @param layout the layout to use
+     * @param d the dimensions to use
+     */
+    void setGraphLayout(Layout<V,E> layout, Dimension d);
+
+    /**
+     * @return the current graph layout
+     */
+    Layout<V,E> getGraphLayout();
+
+    /**
+     * Register <code>l</code> as a listeners to changes in the model. The View registers
+     * in order to repaint itself when the model changes.
+     * @param l the listener to add
+     */
+    void addChangeListener(ChangeListener l);
+
+    /**
+     * Removes a ChangeListener.
+     * @param l the listener to be removed
+     */
+    void removeChangeListener(ChangeListener l);
+
+    /**
+     * Returns an array of all the <code>ChangeListener</code>s added
+     * with addChangeListener().
+     *
+     * @return all of the <code>ChangeListener</code>s added or an empty
+     *         array if no listeners have been added
+     */
+    ChangeListener[] getChangeListeners();
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/VisualizationServer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/VisualizationServer.java
new file mode 100644
index 0000000..07c57fa
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/VisualizationServer.java
@@ -0,0 +1,198 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.visualization;
+
+import java.awt.Graphics;
+import java.awt.RenderingHints.Key;
+import java.awt.geom.Point2D;
+import java.util.Map;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.EventListenerList;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+
+/**
+ * @author tom
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+public interface VisualizationServer<V, E> {
+
+    /**
+     * Specify whether this class uses its offscreen image or not. 
+     * 
+     * @param doubleBuffered if true, then doubleBuffering in the superclass is set to 'false'
+     */
+    void setDoubleBuffered(boolean doubleBuffered);
+
+    /**
+     * Returns whether this class uses double buffering. The superclass
+     * will be the opposite state.
+     * @return the double buffered state 
+     */
+    boolean isDoubleBuffered();
+
+    /**
+     * @return the model.
+     */
+    VisualizationModel<V, E> getModel();
+
+    /**
+     * @param model the model for this class to use
+     */
+    void setModel(VisualizationModel<V, E> model);
+
+    /**
+     * In response to changes from the model, repaint the
+     * view, then fire an event to any listeners.
+     * Examples of listeners are the GraphZoomScrollPane and
+     * the BirdsEyeVisualizationViewer
+     * @param e the change event
+     */
+    void stateChanged(ChangeEvent e);
+
+    /**
+     * Sets the showing Renderer to be the input Renderer. Also
+     * tells the Renderer to refer to this instance
+     * as a PickedKey. (Because Renderers maintain a small
+     * amount of state, such as the PickedKey, it is important
+     * to create a separate instance for each VV instance.)
+     * @param r the renderer to use
+     */
+    void setRenderer(Renderer<V, E> r);
+
+    /**
+     * @return the renderer used by this instance.
+     */
+    Renderer<V, E> getRenderer();
+
+    /**
+     * Replaces the current graph layout with {@code layout}.
+     * @param layout the new layout to set
+     */
+    void setGraphLayout(Layout<V, E> layout);
+
+    /**
+     * @return the current graph layout.
+     */
+    Layout<V, E> getGraphLayout();
+
+    /** 
+     * Makes the component visible if {@code aFlag} is true, or invisible if false.
+     * @param aFlag true iff the component should be visible
+     * @see javax.swing.JComponent#setVisible(boolean)
+     */
+    void setVisible(boolean aFlag);
+
+    /**
+     * @return the renderingHints
+     */
+    Map<Key, Object> getRenderingHints();
+
+    /**
+     * @param renderingHints The renderingHints to set.
+     */
+    void setRenderingHints(Map<Key, Object> renderingHints);
+
+    /**
+     * @param paintable The paintable to add.
+     */
+    void addPreRenderPaintable(Paintable paintable);
+
+    /**
+     * @param paintable The paintable to remove.
+     */
+    void removePreRenderPaintable(Paintable paintable);
+
+    /**
+     * @param paintable The paintable to add.
+     */
+    void addPostRenderPaintable(Paintable paintable);
+
+    /**
+     * @param paintable The paintable to remove.
+     */
+    void removePostRenderPaintable(Paintable paintable);
+
+    /**
+     * Adds a <code>ChangeListener</code>.
+     * @param l the listener to be added
+     */
+    void addChangeListener(ChangeListener l);
+
+    /**
+     * Removes a ChangeListener.
+     * @param l the listener to be removed
+     */
+    void removeChangeListener(ChangeListener l);
+
+    /**
+     * Returns an array of all the <code>ChangeListener</code>s added
+     * with addChangeListener().
+     *
+     * @return all of the <code>ChangeListener</code>s added or an empty
+     *         array if no listeners have been added
+     */
+    ChangeListener[] getChangeListeners();
+
+    /**
+     * Notifies all listeners that have registered interest for
+     * notification on this event type.  The event instance 
+     * is lazily created.
+     * @see EventListenerList
+     */
+    void fireStateChanged();
+
+    /**
+     * @return the vertex PickedState instance
+     */
+    PickedState<V> getPickedVertexState();
+
+    /**
+     * @return the edge PickedState instance
+     */
+    PickedState<E> getPickedEdgeState();
+
+    void setPickedVertexState(PickedState<V> pickedVertexState);
+
+    void setPickedEdgeState(PickedState<E> pickedEdgeState);
+
+    /**
+     * @return the GraphElementAccessor
+     */
+    GraphElementAccessor<V, E> getPickSupport();
+
+    /**
+     * @param pickSupport The pickSupport to set.
+     */
+    void setPickSupport(GraphElementAccessor<V, E> pickSupport);
+
+    Point2D getCenter();
+
+    RenderContext<V, E> getRenderContext();
+
+    void setRenderContext(RenderContext<V, E> renderContext);
+    
+    void repaint();
+    
+    /**
+     * an interface for the preRender and postRender
+     */
+    interface Paintable {
+        public void paint(Graphics g);
+        public boolean useTransform();
+    }
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/VisualizationViewer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/VisualizationViewer.java
new file mode 100644
index 0000000..cae294b
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/VisualizationViewer.java
@@ -0,0 +1,191 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.visualization;
+
+import java.awt.Dimension;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelListener;
+import java.awt.geom.Point2D;
+
+import javax.swing.ToolTipManager;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.visualization.control.GraphMouseListener;
+import edu.uci.ics.jung.visualization.control.MouseListenerTranslator;
+
+/**
+ * Adds mouse behaviors and tooltips to the graph visualization base class
+ * 
+ * @author Joshua O'Madadhain
+ * @author Tom Nelson 
+ * @author Danyel Fisher
+ */
+ at SuppressWarnings("serial")
+public class VisualizationViewer<V,E> extends BasicVisualizationServer<V,E> {
+
+	protected Function<? super V,String> vertexToolTipTransformer;
+	protected Function<? super E,String> edgeToolTipTransformer;
+	protected Function<MouseEvent,String> mouseEventToolTipTransformer;
+	
+    /**
+     * provides MouseListener, MouseMotionListener, and MouseWheelListener
+     * events to the graph
+     */
+    protected GraphMouse graphMouse;
+    
+    protected MouseListener requestFocusListener = new MouseAdapter() {
+		public void mouseClicked(MouseEvent e) {
+			requestFocusInWindow();
+		}
+    };
+
+
+	public VisualizationViewer(Layout<V,E> layout) {
+	    this(new DefaultVisualizationModel<V,E>(layout));
+	}
+	
+	public VisualizationViewer(Layout<V,E> layout, Dimension preferredSize) {
+	    this(new DefaultVisualizationModel<V,E>(layout, preferredSize), preferredSize);
+	}
+	
+	public VisualizationViewer(VisualizationModel<V,E> model) {
+	    this(model, new Dimension(600,600));
+	}
+
+    public VisualizationViewer(VisualizationModel<V,E> model,
+	        Dimension preferredSize) {
+        super(model, preferredSize);
+		setFocusable(true);
+        addMouseListener(requestFocusListener);
+	}
+	
+	/**
+	 * a setter for the GraphMouse. This will remove any
+	 * previous GraphMouse (including the one that
+	 * is added in the initMouseClicker method.
+	 * @param graphMouse new value
+	 */
+	public void setGraphMouse(GraphMouse graphMouse) {
+	    this.graphMouse = graphMouse;
+	    MouseListener[] ml = getMouseListeners();
+	    for(int i=0; i<ml.length; i++) {
+	        if(ml[i] instanceof GraphMouse) {
+	            removeMouseListener(ml[i]);
+	        }
+	    }
+	    MouseMotionListener[] mml = getMouseMotionListeners();
+	    for(int i=0; i<mml.length; i++) {
+	        if(mml[i] instanceof GraphMouse) {
+	            removeMouseMotionListener(mml[i]);
+	        }
+	    }
+	    MouseWheelListener[] mwl = getMouseWheelListeners();
+	    for(int i=0; i<mwl.length; i++) {
+	        if(mwl[i] instanceof GraphMouse) {
+	            removeMouseWheelListener(mwl[i]);
+	        }
+	    }
+	    addMouseListener(graphMouse);
+	    addMouseMotionListener(graphMouse);
+	    addMouseWheelListener(graphMouse);
+	}
+	
+	/**
+	 * @return the current <code>GraphMouse</code>
+	 */
+	public GraphMouse getGraphMouse() {
+	    return graphMouse;
+	}
+
+	/**
+	 * This is the interface for adding a mouse listener. The GEL
+	 * will be called back with mouse clicks on vertices.
+	 * @param gel the mouse listener to add
+	 */
+	public void addGraphMouseListener( GraphMouseListener<V> gel ) {
+		addMouseListener( new MouseListenerTranslator<V,E>( gel, this ));
+	}
+	
+	/** 
+	 * Override to request focus on mouse enter, if a key listener is added
+	 * @see java.awt.Component#addKeyListener(java.awt.event.KeyListener)
+	 */
+	@Override
+	public synchronized void addKeyListener(KeyListener l) {
+		super.addKeyListener(l);
+	}
+	
+	/**
+	 * @param edgeToolTipTransformer the edgeToolTipTransformer to set
+	 */
+	public void setEdgeToolTipTransformer(
+			Function<? super E, String> edgeToolTipTransformer) {
+		this.edgeToolTipTransformer = edgeToolTipTransformer;
+		ToolTipManager.sharedInstance().registerComponent(this);
+	}
+
+	/**
+	 * @param mouseEventToolTipTransformer the mouseEventToolTipTransformer to set
+	 */
+	public void setMouseEventToolTipTransformer(
+			Function<MouseEvent, String> mouseEventToolTipTransformer) {
+		this.mouseEventToolTipTransformer = mouseEventToolTipTransformer;
+		ToolTipManager.sharedInstance().registerComponent(this);
+	}
+
+	/**
+	 * @param vertexToolTipTransformer the vertexToolTipTransformer to set
+	 */
+	public void setVertexToolTipTransformer(
+			Function<? super V, String> vertexToolTipTransformer) {
+		this.vertexToolTipTransformer = vertexToolTipTransformer;
+		ToolTipManager.sharedInstance().registerComponent(this);
+	}
+
+    /**
+     * called by the superclass to display tooltips
+     */
+    public String getToolTipText(MouseEvent event) {
+        Layout<V,E> layout = getGraphLayout();
+        Point2D p = null;
+        if(vertexToolTipTransformer != null) {
+            p = event.getPoint();
+            	//renderContext.getBasicTransformer().inverseViewTransform(event.getPoint());
+            V vertex = getPickSupport().getVertex(layout, p.getX(), p.getY());
+            if(vertex != null) {
+            	return vertexToolTipTransformer.apply(vertex);
+            }
+        }
+        if(edgeToolTipTransformer != null) {
+        	if(p == null) p = renderContext.getMultiLayerTransformer().inverseTransform(Layer.VIEW, event.getPoint());
+            E edge = getPickSupport().getEdge(layout, p.getX(), p.getY());
+            if(edge != null) {
+            	return edgeToolTipTransformer.apply(edge);
+            }
+        }
+        if(mouseEventToolTipTransformer != null) {
+        	return mouseEventToolTipTransformer.apply(event);
+        }
+        return super.getToolTipText(event);
+    }
+
+    /**
+     * a convenience type to represent a class that
+     * processes all types of mouse events for the graph
+     */
+    public interface GraphMouse extends MouseListener, MouseMotionListener, MouseWheelListener {}
+    
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotatingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotatingGraphMousePlugin.java
new file mode 100644
index 0000000..91a01a6
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotatingGraphMousePlugin.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ */
+package edu.uci.ics.jung.visualization.annotations;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RectangularShape;
+
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+
+import edu.uci.ics.jung.visualization.MultiLayerTransformer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.VisualizationServer.Paintable;
+import edu.uci.ics.jung.visualization.control.AbstractGraphMousePlugin;
+
+/** 
+ * AnnotatingGraphMousePlugin can create Shape and Text annotations
+ * in a layer of the graph visualization.
+ * 
+ * @author Tom Nelson
+ */
+public class AnnotatingGraphMousePlugin<V, E> extends AbstractGraphMousePlugin
+    implements MouseListener, MouseMotionListener {
+
+    /**
+     * additional modifiers for the action of adding to an existing
+     * selection
+     */
+    protected int additionalModifiers;
+    
+    /**
+     * used to draw a Shape annotation
+     */
+    protected RectangularShape rectangularShape = new Rectangle2D.Float();
+    
+    /**
+     * the Paintable for the Shape annotation
+     */
+    protected Paintable lensPaintable;
+    
+    /**
+     * a Paintable to store all Annotations
+     */
+    protected AnnotationManager annotationManager;
+    
+    /**
+     * color for annotations
+     */
+    protected Color annotationColor = Color.cyan;
+    
+    /**
+     * layer for annotations
+     */
+    protected Annotation.Layer layer = Annotation.Layer.LOWER;
+    
+    protected boolean fill;
+    
+    /**
+     * holds rendering transforms
+     */
+    protected MultiLayerTransformer basicTransformer;
+    
+    /**
+     * holds rendering settings
+     */
+    protected RenderContext<V,E> rc;
+    
+    /**
+     * set to true when the AnnotationPaintable has been
+     * added to the view component
+     */
+    protected boolean added = false;
+    
+    /**
+     * Create an instance with defaults for primary (button 1) and secondary 
+     * (button 1 + shift) selection.
+     * @param rc the RenderContext for which this plugin will be used
+     */
+	public AnnotatingGraphMousePlugin(RenderContext<V,E> rc) {
+	    this(rc, InputEvent.BUTTON1_MASK, 
+	    		InputEvent.BUTTON1_MASK | InputEvent.SHIFT_MASK);
+	}
+
+	/**
+	 * Create an instance with the specified primary and secondary selection
+	 * mechanisms.
+     * @param rc the RenderContext for which this plugin will be used
+	 * @param selectionModifiers for primary selection
+	 * @param additionalModifiers for additional selection
+	 */
+    public AnnotatingGraphMousePlugin(RenderContext<V,E> rc,
+    		int selectionModifiers, int additionalModifiers) {
+        super(selectionModifiers);
+        this.rc = rc;
+        this.basicTransformer = rc.getMultiLayerTransformer();
+        this.additionalModifiers = additionalModifiers;
+        this.lensPaintable = new LensPaintable();
+        this.annotationManager = new AnnotationManager(rc);
+        this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+    }
+    
+    /**
+     * @return Returns the lensColor.
+     */
+    public Color getAnnotationColor() {
+        return annotationColor;
+    }
+
+    /**
+     * @param lensColor The lensColor to set.
+     */
+    public void setAnnotationColor(Color lensColor) {
+        this.annotationColor = lensColor;
+    }
+
+    /**
+     * the Paintable that draws a Shape annotation
+     * only while it is being created
+     * 
+     */
+    class LensPaintable implements Paintable {
+
+        public void paint(Graphics g) {
+            Color oldColor = g.getColor();
+            g.setColor(annotationColor);
+            ((Graphics2D)g).draw(rectangularShape);
+            g.setColor(oldColor);
+        }
+
+        public boolean useTransform() {
+            return false;
+        }
+    }
+
+    /**
+     * Sets the location for an Annotation.
+     * Will either pop up a dialog to prompt for text
+     * input for a text annotation, or begin the process
+     * of drawing a Shape annotation
+     * 
+	 * @param e the event
+	 */
+    @SuppressWarnings("unchecked")
+    public void mousePressed(MouseEvent e) {
+    	VisualizationViewer<V,E> vv = (VisualizationViewer<V,E>)e.getSource();
+    	down = e.getPoint();
+    	
+		if(added == false) {
+			vv.addPreRenderPaintable(annotationManager.getLowerAnnotationPaintable());
+			vv.addPostRenderPaintable(annotationManager.getUpperAnnotationPaintable());
+			added = true;
+		}
+
+    	
+    	if(e.isPopupTrigger()) {
+    		String annotationString = JOptionPane.showInputDialog(vv,"Annotation:");
+    		if(annotationString != null && annotationString.length() > 0) {
+    			Point2D p = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(down);
+    			Annotation<String> annotation =
+    				new Annotation<String>(annotationString, layer, annotationColor, fill, p);
+    			annotationManager.add(layer, annotation);
+    		}
+    	} else if(e.getModifiers() == additionalModifiers) {
+    		Annotation<?> annotation = annotationManager.getAnnotation(down);
+    		annotationManager.remove(annotation);
+    	} else if(e.getModifiers() == modifiers) {
+    		rectangularShape.setFrameFromDiagonal(down,down);
+    		vv.addPostRenderPaintable(lensPaintable);
+    	}
+    	vv.repaint();
+    }
+
+    /**
+	 * Completes the process of adding a Shape annotation
+	 * and removed the transient paintable
+	 * 
+	 */
+    @SuppressWarnings("unchecked")
+    public void mouseReleased(MouseEvent e) {
+        VisualizationViewer<V,E> vv = (VisualizationViewer<V, E>)e.getSource();
+    	if(e.isPopupTrigger()) {
+    		String annotationString = JOptionPane.showInputDialog(vv,"Annotation:");
+    		if(annotationString != null && annotationString.length() > 0) {
+    			Point2D p = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(down);
+    			Annotation<String> annotation =
+    				new Annotation<String>(annotationString, layer, annotationColor, fill, p);
+    			annotationManager.add(layer, annotation);
+    		}
+    	} else if(e.getModifiers() == modifiers) {
+        	if(down != null) {
+        		Point2D out = e.getPoint();
+        		RectangularShape arect = (RectangularShape)rectangularShape.clone();
+        		arect.setFrameFromDiagonal(down,out);
+        		Shape s = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(arect);
+        		Annotation<Shape> annotation =
+        			new Annotation<Shape>(s, layer, annotationColor, fill, out);
+        		annotationManager.add(layer, annotation);
+        	}
+        }
+        down = null;
+        vv.removePostRenderPaintable(lensPaintable);
+        vv.repaint();
+    }
+    
+    /**
+	 * Draws the transient Paintable that will become
+	 * a Shape annotation when the mouse button is
+	 * released
+	 * 
+	 */
+    @SuppressWarnings("unchecked")
+    public void mouseDragged(MouseEvent e) {
+        VisualizationViewer<V,E> vv = (VisualizationViewer<V, E>)e.getSource();
+
+    	Point2D out = e.getPoint();
+    	if(e.getModifiers() == additionalModifiers) {
+            rectangularShape.setFrameFromDiagonal(down,out);
+    		
+    	} else if(e.getModifiers() == modifiers) {
+            rectangularShape.setFrameFromDiagonal(down,out);
+    		
+    	}
+        rectangularShape.setFrameFromDiagonal(down,out);
+        vv.repaint();
+    }
+    
+     public void mouseClicked(MouseEvent e) {
+    }
+
+    public void mouseEntered(MouseEvent e) {
+        JComponent c = (JComponent)e.getSource();
+        c.setCursor(cursor);
+    }
+
+    public void mouseExited(MouseEvent e) {
+        JComponent c = (JComponent)e.getSource();
+        c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    }
+
+    public void mouseMoved(MouseEvent e) {
+    }
+
+	/**
+	 * @return the rect
+	 */
+	public RectangularShape getRectangularShape() {
+		return rectangularShape;
+	}
+
+	/**
+	 * @param rect the rect to set
+	 */
+	public void setRectangularShape(RectangularShape rect) {
+		this.rectangularShape = rect;
+	}
+
+	/**
+	 * @return the layer
+	 */
+	public Annotation.Layer getLayer() {
+		return layer;
+	}
+
+	/**
+	 * @param layer the layer to set
+	 */
+	public void setLayer(Annotation.Layer layer) {
+		this.layer = layer;
+	}
+
+	/**
+	 * @return the fill
+	 */
+	public boolean isFill() {
+		return fill;
+	}
+
+	/**
+	 * @param fill the fill to set
+	 */
+	public void setFill(boolean fill) {
+		this.fill = fill;
+	}
+
+ }
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotatingModalGraphMouse.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotatingModalGraphMouse.java
new file mode 100644
index 0000000..61e1876
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotatingModalGraphMouse.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * 
+ */
+package edu.uci.ics.jung.visualization.annotations;
+
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.ItemSelectable;
+import java.awt.event.InputEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import javax.swing.ButtonGroup;
+import javax.swing.Icon;
+import javax.swing.JComboBox;
+import javax.swing.JMenu;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.plaf.basic.BasicIconFactory;
+
+import edu.uci.ics.jung.visualization.MultiLayerTransformer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.control.AbstractModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.AnimatedPickingGraphMousePlugin;
+import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.PickingGraphMousePlugin;
+import edu.uci.ics.jung.visualization.control.RotatingGraphMousePlugin;
+import edu.uci.ics.jung.visualization.control.ScalingGraphMousePlugin;
+import edu.uci.ics.jung.visualization.control.ShearingGraphMousePlugin;
+import edu.uci.ics.jung.visualization.control.TranslatingGraphMousePlugin;
+
+/**
+ * a graph mouse that supplies an annotations mode
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+public class AnnotatingModalGraphMouse<V,E> extends AbstractModalGraphMouse 
+	implements ModalGraphMouse, ItemSelectable {
+
+	protected AnnotatingGraphMousePlugin<V,E> annotatingPlugin;
+	protected MultiLayerTransformer basicTransformer;
+	protected RenderContext<V,E> rc;
+
+	/**
+	 * Create an instance with default values for scale in (1.1) and scale out (1/1.1).
+	 * 
+	 * @param rc the RenderContext for which this class will be used
+	 * @param annotatingPlugin the plugin used by this class for annotating
+	 */
+	public AnnotatingModalGraphMouse(RenderContext<V,E> rc, 
+			AnnotatingGraphMousePlugin<V,E> annotatingPlugin) {
+		this(rc, annotatingPlugin, 1.1f, 1/1.1f);
+	}
+
+	/**
+	 * Create an instance with the specified scale in and scale out values.
+	 * 
+	 * @param rc the RenderContext for which this class will be used
+	 * @param annotatingPlugin the plugin used by this class for annotating
+	 * @param in override value for scale in
+	 * @param out override value for scale out
+	 */
+	public AnnotatingModalGraphMouse(RenderContext<V,E> rc,
+			AnnotatingGraphMousePlugin<V,E> annotatingPlugin,
+			float in, float out) {
+		super(in,out);
+		this.rc = rc;
+		this.basicTransformer = rc.getMultiLayerTransformer();
+		this.annotatingPlugin = annotatingPlugin;
+		loadPlugins();
+		setModeKeyListener(new ModeKeyAdapter(this));
+	}
+
+	/**
+	 * create the plugins, and load the plugins for TRANSFORMING mode
+	 *
+	 */
+	@Override
+    protected void loadPlugins() {
+		this.pickingPlugin = new PickingGraphMousePlugin<V,E>();
+		this.animatedPickingPlugin = new AnimatedPickingGraphMousePlugin<V,E>();
+		this.translatingPlugin = new TranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK);
+		this.scalingPlugin = new ScalingGraphMousePlugin(new CrossoverScalingControl(), 0, in, out);
+		this.rotatingPlugin = new RotatingGraphMousePlugin();
+		this.shearingPlugin = new ShearingGraphMousePlugin();
+		add(scalingPlugin);
+		setMode(Mode.TRANSFORMING);
+	}
+
+	/**
+	 * setter for the Mode.
+	 */
+	@Override
+	public void setMode(Mode mode) {
+		if(this.mode != mode) {
+			fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED,
+					this.mode, ItemEvent.DESELECTED));
+			this.mode = mode;
+			if(mode == Mode.TRANSFORMING) {
+				setTransformingMode();
+			} else if(mode == Mode.PICKING) {
+				setPickingMode();
+			} else if(mode == Mode.ANNOTATING) {
+				setAnnotatingMode();
+			}
+			if(modeBox != null) {
+				modeBox.setSelectedItem(mode);
+			}
+			fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, mode, ItemEvent.SELECTED));
+		}
+	}
+	
+	@Override
+	protected void setPickingMode() {
+		remove(translatingPlugin);
+		remove(rotatingPlugin);
+		remove(shearingPlugin);
+		remove(annotatingPlugin);
+		add(pickingPlugin);
+		add(animatedPickingPlugin);
+	}
+
+	@Override
+	protected void setTransformingMode() {
+		remove(pickingPlugin);
+		remove(animatedPickingPlugin);
+		remove(annotatingPlugin);
+		add(translatingPlugin);
+		add(rotatingPlugin);
+		add(shearingPlugin);
+	}
+
+	protected void setEditingMode() {
+		remove(pickingPlugin);
+		remove(animatedPickingPlugin);
+		remove(translatingPlugin);
+		remove(rotatingPlugin);
+		remove(shearingPlugin);
+		remove(annotatingPlugin);
+	}
+
+	protected void setAnnotatingMode() {
+		remove(pickingPlugin);
+		remove(animatedPickingPlugin);
+		remove(translatingPlugin);
+		remove(rotatingPlugin);
+		remove(shearingPlugin);
+		add(annotatingPlugin);
+	}
+
+
+	/**
+	 * @return Returns the modeBox.
+	 */
+	@Override
+	public JComboBox<Mode> getModeComboBox() {
+		if(modeBox == null) {
+			modeBox = new JComboBox<Mode>(
+				new Mode[]{Mode.TRANSFORMING, Mode.PICKING, Mode.ANNOTATING});
+			modeBox.addItemListener(getModeListener());
+		}
+		modeBox.setSelectedItem(mode);
+		return modeBox;
+	}
+
+	/**
+	 * create (if necessary) and return a menu that will change
+	 * the mode
+	 * @return the menu
+	 */
+	@Override
+    public JMenu getModeMenu() {
+		if(modeMenu == null) {
+			modeMenu = new JMenu();// {
+			Icon icon = BasicIconFactory.getMenuArrowIcon();
+			modeMenu.setIcon(BasicIconFactory.getMenuArrowIcon());
+			modeMenu.setPreferredSize(new Dimension(icon.getIconWidth()+10, 
+					icon.getIconHeight()+10));
+
+			final JRadioButtonMenuItem transformingButton = 
+				new JRadioButtonMenuItem(Mode.TRANSFORMING.toString());
+			transformingButton.addItemListener(new ItemListener() {
+				public void itemStateChanged(ItemEvent e) {
+					if(e.getStateChange() == ItemEvent.SELECTED) {
+						setMode(Mode.TRANSFORMING);
+					}
+				}});
+
+			final JRadioButtonMenuItem pickingButton =
+				new JRadioButtonMenuItem(Mode.PICKING.toString());
+			pickingButton.addItemListener(new ItemListener() {
+				public void itemStateChanged(ItemEvent e) {
+					if(e.getStateChange() == ItemEvent.SELECTED) {
+						setMode(Mode.PICKING);
+					}
+				}});
+
+			ButtonGroup radio = new ButtonGroup();
+			radio.add(transformingButton);
+			radio.add(pickingButton);
+			transformingButton.setSelected(true);
+			modeMenu.add(transformingButton);
+			modeMenu.add(pickingButton);
+			modeMenu.setToolTipText("Menu for setting Mouse Mode");
+			addItemListener(new ItemListener() {
+				public void itemStateChanged(ItemEvent e) {
+					if(e.getStateChange() == ItemEvent.SELECTED) {
+						if(e.getItem() == Mode.TRANSFORMING) {
+							transformingButton.setSelected(true);
+						} else if(e.getItem() == Mode.PICKING) {
+							pickingButton.setSelected(true);
+						}
+					}
+				}});
+		}
+		return modeMenu;
+	}
+	
+    public static class ModeKeyAdapter extends KeyAdapter {
+    	private char t = 't';
+    	private char p = 'p';
+    	private char a = 'a';
+    	protected ModalGraphMouse graphMouse;
+
+    	public ModeKeyAdapter(ModalGraphMouse graphMouse) {
+			this.graphMouse = graphMouse;
+		}
+
+		public ModeKeyAdapter(char t, char p, char a, ModalGraphMouse graphMouse) {
+			this.t = t;
+			this.p = p;
+			this.a = a;
+			this.graphMouse = graphMouse;
+		}
+		
+		@Override
+    public void keyTyped(KeyEvent event) {
+			char keyChar = event.getKeyChar();
+			if(keyChar == t) {
+				((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+				graphMouse.setMode(Mode.TRANSFORMING);
+			} else if(keyChar == p) {
+				((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+				graphMouse.setMode(Mode.PICKING);
+			} else if(keyChar == a) {
+				((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
+				graphMouse.setMode(Mode.ANNOTATING);
+			}
+		}
+    }
+}
+
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/Annotation.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/Annotation.java
new file mode 100644
index 0000000..6a19f41
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/Annotation.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * 
+ */
+package edu.uci.ics.jung.visualization.annotations;
+
+import java.awt.Paint;
+import java.awt.geom.Point2D;
+
+/**
+ * stores an annotation, either a shape or a string
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ * @param <T> the type of the annotation object
+ */
+public class Annotation<T> {
+	
+	protected T annotation;
+	protected Paint paint;
+	protected Point2D location;
+	protected Layer layer;
+	protected boolean fill;
+	public static enum Layer { LOWER, UPPER }
+	
+	public Annotation(T annotation, Layer layer, Paint paint, 
+			boolean fill, Point2D location) {
+		this.annotation = annotation;
+		this.layer = layer;
+		this.paint = paint;
+		this.fill = fill;
+		this.location = location;
+	}
+	/**
+	 * @return the annotation
+	 */
+	public T getAnnotation() {
+		return annotation;
+	}
+	/**
+	 * @param annotation the annotation to set
+	 */
+	public void setAnnotation(T annotation) {
+		this.annotation = annotation;
+	}
+	/**
+	 * @return the location
+	 */
+	public Point2D getLocation() {
+		return location;
+	}
+	/**
+	 * @return the layer
+	 */
+	public Layer getLayer() {
+		return layer;
+	}
+	/**
+	 * @param layer the layer to set
+	 */
+	public void setLayer(Layer layer) {
+		this.layer = layer;
+	}
+	/**
+	 * @param location the location to set
+	 */
+	public void setLocation(Point2D location) {
+		this.location = location;
+	}
+	/**
+	 * @return the paint
+	 */
+	public Paint getPaint() {
+		return paint;
+	}
+	/**
+	 * @param paint the paint to set
+	 */
+	public void setPaint(Paint paint) {
+		this.paint = paint;
+	}
+	/**
+	 * @return the fill
+	 */
+	public boolean isFill() {
+		return fill;
+	}
+	/**
+	 * @param fill the fill to set
+	 */
+	public void setFill(boolean fill) {
+		this.fill = fill;
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotationControls.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotationControls.java
new file mode 100644
index 0000000..c4e34de
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotationControls.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * 
+ */
+package edu.uci.ics.jung.visualization.annotations;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Shape;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RectangularShape;
+import java.awt.geom.RoundRectangle2D;
+
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.JButton;
+import javax.swing.JColorChooser;
+import javax.swing.JComboBox;
+import javax.swing.JList;
+import javax.swing.JToggleButton;
+import javax.swing.JToolBar;
+
+/**
+ * a collection of controls for annotations.
+ * allows selection of colors, shapes, etc
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ */
+public class AnnotationControls<V,E> {
+	
+	protected AnnotatingGraphMousePlugin<V,E> annotatingPlugin;
+
+	public AnnotationControls(AnnotatingGraphMousePlugin<V,E> annotatingPlugin) {
+		this.annotatingPlugin = annotatingPlugin;
+	}
+	
+    @SuppressWarnings("serial")
+    public JComboBox<Shape> getShapeBox() {
+    	JComboBox<Shape> shapeBox = new JComboBox<Shape>(
+    			new Shape[] {
+    					new Rectangle2D.Double(),
+    					new RoundRectangle2D.Double(0,0,0,0,50,50),
+    					new Ellipse2D.Double()
+    			});
+    	shapeBox.setRenderer(new DefaultListCellRenderer() {
+    		@Override
+            public Component getListCellRendererComponent(JList<?> list, Object value,
+    			int index, boolean isSelected, boolean hasFocus) {
+    			String valueString = value.toString();
+    			valueString = valueString.substring(0,valueString.indexOf("2D"));
+    			valueString = valueString.substring(valueString.lastIndexOf('.')+1);
+    			return super.getListCellRendererComponent(list, valueString, index,
+    					isSelected, hasFocus);
+    		}
+    	});
+    	shapeBox.addItemListener(new ItemListener() {
+
+			public void itemStateChanged(ItemEvent e) {
+				if(e.getStateChange() == ItemEvent.SELECTED) {
+					annotatingPlugin.setRectangularShape((RectangularShape)e.getItem());
+				}
+				
+			}});
+    	return shapeBox;
+    }
+    
+    public JButton getColorChooserButton() {
+    	final JButton colorChooser = new JButton("Color");
+    	colorChooser.setForeground(annotatingPlugin.getAnnotationColor());
+    	colorChooser.addActionListener(new ActionListener() {
+
+			public void actionPerformed(ActionEvent e) {
+				Color color = JColorChooser.showDialog(colorChooser, "Annotation Color", 
+						colorChooser.getForeground());
+				annotatingPlugin.setAnnotationColor(color);
+				colorChooser.setForeground(color);
+			}});
+    	return colorChooser;
+    }
+    
+    public JComboBox<Annotation.Layer> getLayerBox() {
+    	final JComboBox<Annotation.Layer> layerBox = new JComboBox<Annotation.Layer>(
+    			new Annotation.Layer[] {
+    			Annotation.Layer.LOWER, Annotation.Layer.UPPER
+    			});
+    	layerBox.addItemListener(new ItemListener() {
+
+			public void itemStateChanged(ItemEvent e) {
+				if(e.getStateChange() == ItemEvent.SELECTED) {
+					annotatingPlugin.setLayer((Annotation.Layer)e.getItem());
+				}
+				
+			}});
+
+    	return layerBox;
+    }
+
+    public JToggleButton getFillButton() {
+    	JToggleButton fillButton = new JToggleButton("Fill");
+    	fillButton.addItemListener(new ItemListener() {
+
+			public void itemStateChanged(ItemEvent e) {
+				annotatingPlugin.setFill(e.getStateChange() == ItemEvent.SELECTED);
+				
+			}});
+    	return fillButton;
+    }
+    
+    public JToolBar getAnnotationsToolBar() {
+    	JToolBar toolBar = new JToolBar();
+    	toolBar.add(this.getShapeBox());
+    	toolBar.add(this.getColorChooserButton());
+    	toolBar.add(this.getFillButton());
+    	toolBar.add(this.getLayerBox());
+    	return toolBar;
+    	
+    }
+
+	
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotationManager.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotationManager.java
new file mode 100644
index 0000000..a6292bb
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotationManager.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * 
+ */
+package edu.uci.ics.jung.visualization.annotations;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.transform.AffineTransformer;
+import edu.uci.ics.jung.visualization.transform.LensTransformer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/**
+ * handles the selection of annotations, and the support for the
+ * tools to draw them at specific layers.
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ */
+public class AnnotationManager {
+	
+    protected AnnotationRenderer annotationRenderer = new AnnotationRenderer();
+	protected AnnotationPaintable lowerAnnotationPaintable;
+	protected AnnotationPaintable upperAnnotationPaintable;
+	
+	protected RenderContext<?,?> rc;
+	protected AffineTransformer transformer;
+
+	public AnnotationManager(RenderContext<?,?> rc) {
+		this.rc = rc;
+		this.lowerAnnotationPaintable = new AnnotationPaintable(rc, annotationRenderer);
+		this.upperAnnotationPaintable = new AnnotationPaintable(rc, annotationRenderer);
+		
+		MutableTransformer mt = rc.getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+		if(mt instanceof AffineTransformer) {
+			transformer = (AffineTransformer)mt;
+		} else if(mt instanceof LensTransformer) {
+			transformer = (AffineTransformer)((LensTransformer)mt).getDelegate();
+		}
+
+	}
+	
+	public AnnotationPaintable getAnnotationPaintable(Annotation.Layer layer) {
+		if(layer == Annotation.Layer.LOWER) {
+			return this.lowerAnnotationPaintable;
+		}
+		if(layer == Annotation.Layer.UPPER) {
+			return this.upperAnnotationPaintable;
+		}
+		return null;
+	}
+	
+	public void add(Annotation.Layer layer, Annotation<?> annotation) {
+		if(layer == Annotation.Layer.LOWER) {
+			this.lowerAnnotationPaintable.add(annotation);
+		}
+		if(layer == Annotation.Layer.UPPER) {
+			this.upperAnnotationPaintable.add(annotation);
+		}
+	}
+	
+	public void remove(Annotation<?> annotation) {
+		this.lowerAnnotationPaintable.remove(annotation);
+		this.upperAnnotationPaintable.remove(annotation);
+	}
+	
+	protected AnnotationPaintable getLowerAnnotationPaintable() {
+		return lowerAnnotationPaintable;
+	}
+	
+	protected AnnotationPaintable getUpperAnnotationPaintable() {
+		return upperAnnotationPaintable;
+	}
+	
+    public Annotation<?> getAnnotation(Point2D p) {
+		@SuppressWarnings("rawtypes")
+		Set<Annotation> annotations = new HashSet<Annotation>(lowerAnnotationPaintable.getAnnotations());
+		annotations.addAll(upperAnnotationPaintable.getAnnotations());
+		return getAnnotation(p, annotations);
+	}
+	
+    @SuppressWarnings("rawtypes")
+	public Annotation<?> getAnnotation(Point2D p, Collection<Annotation> annotations) {
+		double closestDistance = Double.MAX_VALUE;
+		Annotation<?> closestAnnotation = null;
+		for(Annotation annotation : annotations) {
+			Object ann = annotation.getAnnotation();
+			if(ann instanceof Shape) {
+				Point2D ip = rc.getMultiLayerTransformer().inverseTransform(p);
+				Shape shape = (Shape)ann;
+				if(shape.contains(ip)) {
+					
+					Rectangle2D shapeBounds = shape.getBounds2D();
+					Point2D shapeCenter = new Point2D.Double(shapeBounds.getCenterX(), shapeBounds.getCenterY());
+					double distanceSq = shapeCenter.distanceSq(ip);
+					if(distanceSq < closestDistance) {
+						closestDistance = distanceSq;
+						closestAnnotation = annotation;
+					}
+				}
+			} else if(ann instanceof String) {
+				
+				Point2D ip = rc.getMultiLayerTransformer().inverseTransform(Layer.VIEW, p);
+				Point2D ap = annotation.getLocation();
+				String label = (String)ann;
+				Component component = prepareRenderer(rc, annotationRenderer, label);
+				
+				AffineTransform base = new AffineTransform(transformer.getTransform());
+				double rotation = transformer.getRotation();
+				// unrotate the annotation
+				AffineTransform unrotate =
+					AffineTransform.getRotateInstance(-rotation, ap.getX(), ap.getY());
+				base.concatenate(unrotate);
+				
+				Dimension d = component.getPreferredSize();
+				Rectangle2D componentBounds = new Rectangle2D.Double(ap.getX(), ap.getY(), d.width, d.height);
+				
+				Shape componentBoundsShape = base.createTransformedShape(componentBounds);
+				Point2D componentCenter = new Point2D.Double(componentBoundsShape.getBounds().getCenterX(),
+						componentBoundsShape.getBounds().getCenterY());
+				if(componentBoundsShape.contains(ip)) {
+					double distanceSq = componentCenter.distanceSq(ip);
+					if(distanceSq < closestDistance) {
+						closestDistance = distanceSq;
+						closestAnnotation = annotation;
+					}
+				}
+				
+			}
+		}
+		return closestAnnotation;
+	}
+	
+	public Component prepareRenderer(RenderContext<?,?> rc, AnnotationRenderer annotationRenderer, Object value) {
+		return annotationRenderer.getAnnotationRendererComponent(rc.getScreenDevice(), value);
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotationPaintable.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotationPaintable.java
new file mode 100644
index 0000000..444bcc8
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotationPaintable.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * 
+ */
+package edu.uci.ics.jung.visualization.annotations;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.JComponent;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.VisualizationServer.Paintable;
+import edu.uci.ics.jung.visualization.transform.AffineTransformer;
+import edu.uci.ics.jung.visualization.transform.LensTransformer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/**
+ * handles the actual drawing of annotations
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ */
+public class AnnotationPaintable implements Paintable {
+	
+    @SuppressWarnings("rawtypes")
+	protected Set<Annotation> annotations = new HashSet<Annotation>();
+    protected AnnotationRenderer annotationRenderer;
+
+	protected RenderContext<?,?> rc;
+	protected AffineTransformer transformer;
+	
+	public AnnotationPaintable(RenderContext<?,?> rc, AnnotationRenderer annotationRenderer) {
+		this.rc = rc;
+		this.annotationRenderer = annotationRenderer;
+		MutableTransformer mt = rc.getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+		if(mt instanceof AffineTransformer) {
+			transformer = (AffineTransformer)mt;
+		} else if(mt instanceof LensTransformer) {
+			transformer = (AffineTransformer)((LensTransformer)mt).getDelegate();
+		}
+	}
+	
+    public void add(Annotation<?> annotation) {
+		annotations.add(annotation);
+	}
+	
+    public void remove(Annotation<?> annotation) {
+		annotations.remove(annotation);
+	}
+	
+    /**
+	 * @return the annotations
+	 */
+    @SuppressWarnings("rawtypes")
+	public Set<Annotation> getAnnotations() {
+		return Collections.unmodifiableSet(annotations);
+	}
+
+    public void paint(Graphics g) {
+    	Graphics2D g2d = (Graphics2D)g;
+        Color oldColor = g.getColor();
+        for(Annotation<?> annotation : annotations) {
+        	Object ann = annotation.getAnnotation();
+        	if(ann instanceof Shape) {
+            	Shape shape = (Shape)ann;
+            	Paint paint = annotation.getPaint();
+            	Shape s = transformer.transform(shape);
+            	g2d.setPaint(paint);
+            	if(annotation.isFill()) {
+            		g2d.fill(s);
+            	} else {
+            		g2d.draw(s);
+            	}
+        	} else if(ann instanceof String) {
+            	Point2D p = annotation.getLocation();
+            	String label = (String)ann;
+                Component component = prepareRenderer(rc, annotationRenderer, label);
+                component.setForeground((Color)annotation.getPaint());
+                if(annotation.isFill()) {
+                	((JComponent)component).setOpaque(true);
+                	component.setBackground((Color)annotation.getPaint());
+                	component.setForeground(Color.black);
+                }
+                Dimension d = component.getPreferredSize();
+                AffineTransform old = g2d.getTransform();
+                AffineTransform base = new AffineTransform(old);
+                AffineTransform xform = transformer.getTransform();
+
+                double rotation = transformer.getRotation();
+                // unrotate the annotation
+                AffineTransform unrotate = 
+                	AffineTransform.getRotateInstance(-rotation, p.getX(), p.getY());
+                base.concatenate(xform);
+                base.concatenate(unrotate);
+                g2d.setTransform(base);
+                rc.getRendererPane().paintComponent(g, component, rc.getScreenDevice(), 
+                        (int)p.getX(), (int)p.getY(),
+                        d.width, d.height, true);
+                g2d.setTransform(old);
+        	}
+        }
+        g.setColor(oldColor);
+    }
+    
+	public Component prepareRenderer(RenderContext<?,?> rc, AnnotationRenderer annotationRenderer, Object value) {
+		return annotationRenderer.getAnnotationRendererComponent(rc.getScreenDevice(), value);
+	}
+
+    public boolean useTransform() {
+        return true;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotationRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotationRenderer.java
new file mode 100644
index 0000000..5703600
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/AnnotationRenderer.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * 
+ */
+
+package edu.uci.ics.jung.visualization.annotations;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Rectangle;
+import java.io.Serializable;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+
+/**
+ * AnnotationRenderer is similar to the cell renderers
+ * used by the JTable and JTree JFC classes.
+ * 
+ * @author Tom Nelson 
+ *
+ * 
+ */
+ at SuppressWarnings("serial")
+public class AnnotationRenderer extends JLabel implements
+        Serializable {
+
+     protected static Border noFocusBorder = new EmptyBorder(0,0,0,0); 
+    
+    /**
+     * Creates a default table cell renderer.
+     */
+    public AnnotationRenderer() {
+        setOpaque(true);
+        setBorder(noFocusBorder);
+    }
+
+    /**
+     * Overrides <code>JComponent.setForeground</code> to assign
+     * the unselected-foreground color to the specified color.
+     * 
+     * @param c set the foreground color to this value
+     */
+    @Override
+    public void setForeground(Color c) {
+        super.setForeground(c); 
+    }
+    
+    /**
+     * Overrides <code>JComponent.setBackground</code> to assign
+     * the unselected-background color to the specified color.
+     *
+     * @param c set the background color to this value
+     */
+    @Override
+    public void setBackground(Color c) {
+        super.setBackground(c); 
+    }
+
+    /**
+     * Notification from the <code>UIManager</code> that the look and feel
+     * has changed.
+     * Replaces the current UI object with the latest version from the 
+     * <code>UIManager</code>.
+     *
+     * @see JComponent#updateUI
+     */
+    @Override
+    public void updateUI() {
+        super.updateUI(); 
+        setForeground(null);
+        setBackground(null);
+    }
+    
+    /**
+     * Returns the default label renderer.
+     *
+     * @param vv  the <code>VisualizationViewer</code> to render on
+     * @param value  the value to assign to the label
+     * @return the default label renderer
+     */
+    public Component getAnnotationRendererComponent(JComponent vv, Object value) {
+        
+        super.setForeground(vv.getForeground());
+        super.setBackground(vv.getBackground());
+        
+        setFont(vv.getFont());
+        setIcon(null);
+        setBorder(noFocusBorder);
+        setValue(value); 
+        return this;
+    }
+    
+    /*
+     * The following methods are overridden as a performance measure to 
+     * to prune code-paths are often called in the case of renders
+     * but which we know are unnecessary.  Great care should be taken
+     * when writing your own renderer to weigh the benefits and 
+     * drawbacks of overriding methods like these.
+     */
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public boolean isOpaque() { 
+        Color back = getBackground();
+        Component p = getParent(); 
+        if (p != null) { 
+            p = p.getParent(); 
+        }
+        boolean colorMatch = (back != null) && (p != null) && 
+        back.equals(p.getBackground()) && 
+        p.isOpaque();
+        return !colorMatch && super.isOpaque(); 
+    }
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void validate() {}
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void revalidate() {}
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void repaint(long tm, int x, int y, int width, int height) {}
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void repaint(Rectangle r) { }
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {	
+        // Strings get interned...
+        if (propertyName=="text") {
+            super.firePropertyChange(propertyName, oldValue, newValue);
+        }
+    }
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { }
+
+    /**
+     * Sets the <code>String</code> object for the cell being rendered to
+     * <code>value</code>.
+     * 
+     * @param value  the string value for this cell; if value is
+     *		<code>null</code> it sets the text value to an empty string
+     * @see JLabel#setText
+     * 
+     */
+    protected void setValue(Object value) {
+        setText((value == null) ? "" : value.toString());
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/package.html b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/package.html
new file mode 100644
index 0000000..ba4dd53
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/annotations/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Classes which support creating visual annotations for graphs.
+
+</body>
+</html>
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AbsoluteCrossoverScalingControl.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AbsoluteCrossoverScalingControl.java
new file mode 100644
index 0000000..157fbe0
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AbsoluteCrossoverScalingControl.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/**
+ * Scales to the absolute value passed as an argument.
+ * It first resets the scaling Functions, then uses
+ * the relative CrossoverScalingControl to achieve the
+ * absolute value.
+ * 
+ * @author Tom Nelson 
+ *
+ */
+public class AbsoluteCrossoverScalingControl extends CrossoverScalingControl
+        implements ScalingControl {
+
+	/**
+     * Scale to the absolute value passed as 'amount'.
+	 * 
+	 * @param vv the VisualizationServer used for rendering; provides the layout and view transformers.
+	 * @param amount the amount by which to scale
+	 * @param at the point of reference for scaling
+	 */
+	public void scale(VisualizationServer<?,?> vv, float amount, Point2D at) {
+        MutableTransformer layoutTransformer
+        	= vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+        MutableTransformer viewTransformer
+        	= vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
+        double modelScale = layoutTransformer.getScale();
+        double viewScale = viewTransformer.getScale();
+        double inverseModelScale = Math.sqrt(crossover)/modelScale;
+        double inverseViewScale = Math.sqrt(crossover)/viewScale;
+        
+        Point2D transformedAt
+        	= vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, at);
+        
+        // return the Functions to 1.0
+        layoutTransformer.scale(inverseModelScale, inverseModelScale, transformedAt);
+        viewTransformer.scale(inverseViewScale, inverseViewScale, at);
+
+        super.scale(vv, amount, at);
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AbstractGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AbstractGraphMousePlugin.java
new file mode 100644
index 0000000..dfc80f8
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AbstractGraphMousePlugin.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 6, 2005
+ */
+
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Cursor;
+import java.awt.Point;
+import java.awt.event.MouseEvent;
+
+/**
+ * a base class for GraphMousePlugin instances. Holds some members
+ * common to all GraphMousePlugins 
+ * @author thomasnelson
+ *
+ */
+public abstract class AbstractGraphMousePlugin implements GraphMousePlugin {
+
+	/**
+	 * modifiers to compare against mouse event modifiers
+	 */
+    protected int modifiers;
+    
+    /**
+     * the location in the View where the mouse was pressed
+     */
+    protected Point down;
+    
+    /**
+     * the special cursor that plugins may display
+     */
+    protected Cursor cursor;
+    
+    /**
+     * Creates an instance with the specified mouse event modifiers.
+     * @param modifiers the mouse event modifiers to use
+     */
+    public AbstractGraphMousePlugin(int modifiers) {
+        this.modifiers = modifiers;
+    }
+    
+    /**
+     * getter for mouse modifiers
+     */
+    public int getModifiers() {
+        return modifiers;
+    }
+
+    /**
+     * setter for mouse modifiers
+     */
+    public void setModifiers(int modifiers) {
+        this.modifiers = modifiers;
+    }
+    
+    /**
+     * check the mouse event modifiers against the
+     * instance member modifiers. Default implementation
+     * checks equality. Can be overridden to test with a mask
+     */
+    public boolean checkModifiers(MouseEvent e) {
+        return e.getModifiers() == modifiers;
+    }
+
+    /**
+     * @return Returns the cursor.
+     */
+    public Cursor getCursor() {
+        return cursor;
+    }
+
+    /**
+     * @param cursor The cursor to set.
+     */
+    public void setCursor(Cursor cursor) {
+        this.cursor = cursor;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AbstractModalGraphMouse.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AbstractModalGraphMouse.java
new file mode 100644
index 0000000..cb5faa7
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AbstractModalGraphMouse.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Dimension;
+import java.awt.ItemSelectable;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyListener;
+
+import javax.swing.ButtonGroup;
+import javax.swing.Icon;
+import javax.swing.JComboBox;
+import javax.swing.JMenu;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.event.EventListenerList;
+import javax.swing.plaf.basic.BasicIconFactory;
+
+
+/** 
+ * 
+ * AbstractModalGraphMouse is a PluggableGraphMouse class that
+ * manages a collection of plugins for picking and
+ * transforming the graph. Additionally, it carries the notion
+ * of a Mode: Picking or Translating. Switching between modes
+ * allows for a more natural choice of mouse modifiers to
+ * be used for the various plugins. The default modifiers are
+ * intended to mimick those of mainstream software applications
+ * in order to be intuitive to users.
+ * 
+ * To change between modes, two different controls are offered,
+ * a combo box and a menu system. These controls are lazily created
+ * in their respective 'getter' methods so they don't impact
+ * code that does not intend to use them.
+ * The menu control can be placed in an unused corner of the
+ * GraphZoomScrollPane, which is a common location for mouse
+ * mode selection menus in mainstream applications.
+ * 
+ * Users must implement the loadPlugins() method to create and
+ * install the GraphMousePlugins. The order of the plugins is
+ * important, as they are evaluated against the mask parameters
+ * in the order that they are added.
+ * 
+ * @author Tom Nelson
+ */
+public abstract class AbstractModalGraphMouse extends PluggableGraphMouse 
+    implements ModalGraphMouse, ItemSelectable {
+    
+	/**
+	 * used by the scaling plugins for zoom in
+	 */
+    protected float in;
+    /**
+     * used by the scaling plugins for zoom out
+     */
+    protected float out;
+    /**
+     * a listener for mode changes
+     */
+    protected ItemListener modeListener;
+    /**
+     * a JComboBox control available to set the mode
+     */
+    protected JComboBox<Mode> modeBox;
+    /**
+     * a menu available to set the mode
+     */
+    protected JMenu modeMenu;
+    /**
+     * the current mode
+     */
+    protected Mode mode;
+    /**
+     * listeners for mode changes
+     */
+    protected EventListenerList listenerList = new EventListenerList();
+
+    protected GraphMousePlugin pickingPlugin;
+    protected GraphMousePlugin translatingPlugin;
+    protected GraphMousePlugin animatedPickingPlugin;
+    protected GraphMousePlugin scalingPlugin;
+    protected GraphMousePlugin rotatingPlugin;
+    protected GraphMousePlugin shearingPlugin;
+    protected KeyListener modeKeyListener;
+    
+
+    protected AbstractModalGraphMouse(float in, float out) {
+		this.in = in;
+		this.out = out;
+	}
+
+	/**
+     * create the plugins, and load the plugins for TRANSFORMING mode
+     *
+     */
+    protected abstract void loadPlugins();
+    
+    /**
+     * setter for the Mode.
+     */
+    public void setMode(Mode mode) {
+        if(this.mode != mode) {
+            fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED,
+                    this.mode, ItemEvent.DESELECTED));
+            this.mode = mode;
+            if(mode == Mode.TRANSFORMING) {
+                setTransformingMode();
+            } else if(mode == Mode.PICKING) {
+                setPickingMode();
+            }
+            if(modeBox != null) {
+                modeBox.setSelectedItem(mode);
+            }
+            fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, mode, ItemEvent.SELECTED));
+        }
+    }
+    /* (non-Javadoc)
+     * @see edu.uci.ics.jung.visualization.control.ModalGraphMouse#setPickingMode()
+     */
+    protected void setPickingMode() {
+        remove(translatingPlugin);
+        remove(rotatingPlugin);
+        remove(shearingPlugin);
+        add(pickingPlugin);
+        add(animatedPickingPlugin);
+    }
+    
+    /* (non-Javadoc)
+     * @see edu.uci.ics.jung.visualization.control.ModalGraphMouse#setTransformingMode()
+     */
+    protected void setTransformingMode() {
+        remove(pickingPlugin);
+        remove(animatedPickingPlugin);
+        add(translatingPlugin);
+        add(rotatingPlugin);
+        add(shearingPlugin);
+    }
+    
+    /**
+     * @param zoomAtMouse The zoomAtMouse to set.
+     */
+    public void setZoomAtMouse(boolean zoomAtMouse) {
+        ((ScalingGraphMousePlugin) scalingPlugin).setZoomAtMouse(zoomAtMouse);
+    }
+    
+    /**
+     * listener to set the mode from an external event source
+     */
+    class ModeListener implements ItemListener {
+        public void itemStateChanged(ItemEvent e) {
+            setMode((Mode) e.getItem());
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see edu.uci.ics.jung.visualization.control.ModalGraphMouse#getModeListener()
+     */
+    public ItemListener getModeListener() {
+		if (modeListener == null) {
+			modeListener = new ModeListener();
+		}
+		return modeListener;
+	}
+    
+    /**
+	 * @return the modeKeyListener
+	 */
+	public KeyListener getModeKeyListener() {
+		return modeKeyListener;
+	}
+
+	/**
+	 * @param modeKeyListener the modeKeyListener to set
+	 */
+	public void setModeKeyListener(KeyListener modeKeyListener) {
+		this.modeKeyListener = modeKeyListener;
+	}
+
+	/**
+	 * @return Returns the modeBox.
+	 */
+    public JComboBox<Mode> getModeComboBox() {
+        if(modeBox == null) {
+            modeBox = new JComboBox<Mode>(new Mode[]{Mode.TRANSFORMING, Mode.PICKING});
+            modeBox.addItemListener(getModeListener());
+        }
+        modeBox.setSelectedItem(mode);
+        return modeBox;
+    }
+    
+    /**
+     * create (if necessary) and return a menu that will change
+     * the mode
+     * @return the menu
+     */
+    public JMenu getModeMenu() {
+        if(modeMenu == null) {
+            modeMenu = new JMenu();// {
+            Icon icon = BasicIconFactory.getMenuArrowIcon();
+            modeMenu.setIcon(BasicIconFactory.getMenuArrowIcon());
+            modeMenu.setPreferredSize(new Dimension(icon.getIconWidth()+10, 
+            		icon.getIconHeight()+10));
+
+            final JRadioButtonMenuItem transformingButton = 
+                new JRadioButtonMenuItem(Mode.TRANSFORMING.toString());
+            transformingButton.addItemListener(new ItemListener() {
+                public void itemStateChanged(ItemEvent e) {
+                    if(e.getStateChange() == ItemEvent.SELECTED) {
+                        setMode(Mode.TRANSFORMING);
+                    }
+                }});
+            
+            final JRadioButtonMenuItem pickingButton =
+                new JRadioButtonMenuItem(Mode.PICKING.toString());
+            pickingButton.addItemListener(new ItemListener() {
+                public void itemStateChanged(ItemEvent e) {
+                    if(e.getStateChange() == ItemEvent.SELECTED) {
+                        setMode(Mode.PICKING);
+                    }
+                }});
+            ButtonGroup radio = new ButtonGroup();
+            radio.add(transformingButton);
+            radio.add(pickingButton);
+            transformingButton.setSelected(true);
+            modeMenu.add(transformingButton);
+            modeMenu.add(pickingButton);
+            modeMenu.setToolTipText("Menu for setting Mouse Mode");
+            addItemListener(new ItemListener() {
+				public void itemStateChanged(ItemEvent e) {
+					if(e.getStateChange() == ItemEvent.SELECTED) {
+						if(e.getItem() == Mode.TRANSFORMING) {
+							transformingButton.setSelected(true);
+						} else if(e.getItem() == Mode.PICKING) {
+							pickingButton.setSelected(true);
+						}
+					}
+				}});
+        }
+        return modeMenu;
+    }
+    
+    /**
+     * add a listener for mode changes
+     */
+    public void addItemListener(ItemListener aListener) {
+        listenerList.add(ItemListener.class,aListener);
+    }
+
+    /**
+     * remove a listener for mode changes
+     */
+    public void removeItemListener(ItemListener aListener) {
+        listenerList.remove(ItemListener.class,aListener);
+    }
+
+    /**
+     * Returns an array of all the <code>ItemListener</code>s added
+     * to this JComboBox with addItemListener().
+     *
+     * @return all of the <code>ItemListener</code>s added or an empty
+     *         array if no listeners have been added
+     * @since 1.4
+     */
+    public ItemListener[] getItemListeners() {
+        return listenerList.getListeners(ItemListener.class);
+    }
+    
+    public Object[] getSelectedObjects() {
+        if ( mode == null )
+            return new Object[0];
+        else {
+            Object result[] = new Object[1];
+            result[0] = mode;
+            return result;
+        }
+    }
+
+    /**
+     * Notifies all listeners that have registered interest for
+     * notification on this event type.
+     * @param e  the event of interest
+     *  
+     * @see EventListenerList
+     */
+    protected void fireItemStateChanged(ItemEvent e) {
+        // Guaranteed to return a non-null array
+        Object[] listeners = listenerList.getListenerList();
+        // Process the listeners last to first, notifying
+        // those that are interested in this event
+        for ( int i = listeners.length-2; i>=0; i-=2 ) {
+            if ( listeners[i]==ItemListener.class ) {
+                ((ItemListener)listeners[i+1]).itemStateChanged(e);
+            }
+        }
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AbstractPopupGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AbstractPopupGraphMousePlugin.java
new file mode 100644
index 0000000..7a5cc1b
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AbstractPopupGraphMousePlugin.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
+public abstract class AbstractPopupGraphMousePlugin extends AbstractGraphMousePlugin 
+    implements MouseListener {
+    
+    public AbstractPopupGraphMousePlugin() {
+        this(MouseEvent.BUTTON3_MASK);
+    }
+    public AbstractPopupGraphMousePlugin(int modifiers) {
+        super(modifiers);
+    }
+    public void mousePressed(MouseEvent e) {
+        if(e.isPopupTrigger()) {
+            handlePopup(e);
+            e.consume();
+        }
+    }
+    
+    /**
+     * if this is the popup trigger, process here, otherwise
+     * defer to the superclass
+     */
+    public void mouseReleased(MouseEvent e) {
+        if(e.isPopupTrigger()) {
+            handlePopup(e);
+            e.consume();
+        }
+    }
+    
+    protected abstract void handlePopup(MouseEvent e);
+    
+    public void mouseClicked(MouseEvent e) {
+    }
+    
+    public void mouseEntered(MouseEvent e) {
+    }
+    
+    public void mouseExited(MouseEvent e) {
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AnimatedPickingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AnimatedPickingGraphMousePlugin.java
new file mode 100644
index 0000000..e2abb27
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/AnimatedPickingGraphMousePlugin.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Cursor;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+
+import javax.swing.JComponent;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+
+/** 
+ * AnimatedPickingGraphMousePlugin supports the picking of one Graph
+ * Vertex. When the mouse is released, the graph is translated so that
+ * the picked Vertex is moved to the center of the view. This translation
+ * is conducted in an animation Thread so that the graph slides to its
+ * new position
+ * 
+ * @author Tom Nelson
+ */
+public class AnimatedPickingGraphMousePlugin<V, E> extends AbstractGraphMousePlugin
+    implements MouseListener, MouseMotionListener {
+
+	/**
+	 * the picked Vertex
+	 */
+    protected V vertex;
+    
+    /**
+	 * Creates an instance with default modifiers of BUTTON1_MASK and CTRL_MASK
+	 */
+	public AnimatedPickingGraphMousePlugin() {
+	    this(InputEvent.BUTTON1_MASK  | InputEvent.CTRL_MASK);
+	}
+
+	/**
+	 * Creates an instance with the specified mouse event modifiers.
+	 * @param selectionModifiers the mouse event modifiers to use.
+	 */
+    public AnimatedPickingGraphMousePlugin(int selectionModifiers) {
+        super(selectionModifiers);
+        this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+    }
+
+	/**
+	 * If the event occurs on a Vertex, pick that single Vertex
+	 * @param e the event
+	 */
+    @SuppressWarnings("unchecked")
+    public void mousePressed(MouseEvent e) {
+		if (e.getModifiers() == modifiers) {
+			VisualizationViewer<V,E> vv = (VisualizationViewer<V,E>) e.getSource();
+			GraphElementAccessor<V, E> pickSupport = vv.getPickSupport();
+			PickedState<V> pickedVertexState = vv.getPickedVertexState();
+            Layout<V,E> layout = vv.getGraphLayout();
+			if (pickSupport != null && pickedVertexState != null) {
+				// p is the screen point for the mouse event
+				Point2D p = e.getPoint();
+				vertex = pickSupport.getVertex(layout, p.getX(), p.getY());
+				if (vertex != null) {
+					if (pickedVertexState.isPicked(vertex) == false) {
+						pickedVertexState.clear();
+						pickedVertexState.pick(vertex, true);
+					}
+				}
+			}
+            e.consume();
+		}
+	}
+
+
+/**
+ * If a Vertex was picked in the mousePressed event, start a Thread
+ * to animate the translation of the graph so that the picked Vertex
+ * moves to the center of the view
+ * 
+ * @param e the event
+ */
+    @SuppressWarnings("unchecked")
+    public void mouseReleased(MouseEvent e) {
+		if (e.getModifiers() == modifiers) {
+			final VisualizationViewer<V,E> vv = (VisualizationViewer<V,E>) e.getSource();
+			Point2D newCenter = null;
+			if (vertex != null) {
+				// center the picked vertex
+				Layout<V,E> layout = vv.getGraphLayout();
+				newCenter = layout.apply(vertex);
+			} else {
+				// they did not pick a vertex to center, so
+				// just center the graph
+				newCenter = vv.getCenter();
+			}
+			Point2D lvc = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(vv.getCenter());
+			final double dx = (lvc.getX() - newCenter.getX()) / 10;
+			final double dy = (lvc.getY() - newCenter.getY()) / 10;
+
+			Runnable animator = new Runnable() {
+
+				public void run() {
+					for (int i = 0; i < 10; i++) {
+						vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).translate(dx, dy);
+						try {
+							Thread.sleep(100);
+						} catch (InterruptedException ex) {
+						}
+					}
+				}
+			};
+			Thread thread = new Thread(animator);
+			thread.start();
+		}
+	}
+    
+    public void mouseClicked(MouseEvent e) {
+    }
+
+    /**
+     * show a special cursor while the mouse is inside the window
+     */
+    public void mouseEntered(MouseEvent e) {
+        JComponent c = (JComponent)e.getSource();
+        c.setCursor(cursor);
+    }
+
+    /**
+     * revert to the default cursor when the mouse leaves this window
+     */
+    public void mouseExited(MouseEvent e) {
+        JComponent c = (JComponent)e.getSource();
+        c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    }
+
+    public void mouseMoved(MouseEvent e) {
+    }
+
+	public void mouseDragged(MouseEvent arg0) {
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/CrossoverScalingControl.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/CrossoverScalingControl.java
new file mode 100644
index 0000000..8a58f6e
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/CrossoverScalingControl.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/** 
+ * A scaling control that has a crossover point.
+ * When the overall scale of the view and
+ * model is less than the crossover point, the scaling is applied
+ * to the view's transform and the graph nodes, labels, etc grow
+ * smaller. This preserves the overall shape of the graph.
+ * When the scale is larger than the crossover, the scaling is
+ * applied to the graph layout. The graph spreads out, but the
+ * vertices and labels grow no larger than their original size. 
+ * 
+ * @author Tom Nelson
+ */
+public class CrossoverScalingControl implements ScalingControl {
+
+    /**
+     * Point where scale crosses over from view to layout.
+     */
+    protected double crossover = 1.0;
+    
+    /**
+     * Sets the crossover point to the specified value.
+     * @param crossover the crossover point to use (defaults to 1.0)
+     */
+	public void setCrossover(double crossover) {
+	    this.crossover = crossover;
+	}
+
+    /**
+     * @return the current crossover value
+     */
+    public double getCrossover() {
+        return crossover;
+    }
+    
+	public void scale(VisualizationServer<?,?> vv, float amount, Point2D at) {
+	        
+	    MutableTransformer layoutTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+	    MutableTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
+	    double modelScale = layoutTransformer.getScale();
+	    double viewScale = viewTransformer.getScale();
+	    double inverseModelScale = Math.sqrt(crossover)/modelScale;
+	    double inverseViewScale = Math.sqrt(crossover)/viewScale;
+	    double scale = modelScale * viewScale;
+	    
+	    Point2D transformedAt = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, at);
+	    
+        if((scale*amount - crossover)*(scale*amount - crossover) < 0.001) {
+            // close to the control point, return both Functions to a scale of sqrt crossover value
+            layoutTransformer.scale(inverseModelScale, inverseModelScale, transformedAt);
+            viewTransformer.scale(inverseViewScale, inverseViewScale, at);
+        } else if(scale*amount < crossover) {
+            // scale the viewTransformer, return the layoutTransformer to sqrt crossover value
+	        viewTransformer.scale(amount, amount, at);
+	        layoutTransformer.scale(inverseModelScale, inverseModelScale, transformedAt);
+	    } else {
+            // scale the layoutTransformer, return the viewTransformer to crossover value
+	        layoutTransformer.scale(amount, amount, transformedAt);
+	        viewTransformer.scale(inverseViewScale, inverseViewScale, at);
+	    }
+	    vv.repaint();
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/CubicCurveEdgeEffects.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/CubicCurveEdgeEffects.java
new file mode 100644
index 0000000..362d8ee
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/CubicCurveEdgeEffects.java
@@ -0,0 +1,140 @@
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.CubicCurve2D;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.BasicVisualizationServer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+import edu.uci.ics.jung.visualization.util.ArrowFactory;
+
+public class CubicCurveEdgeEffects<V,E> implements EdgeEffects<V,E> {
+	
+	protected CubicCurve2D rawEdge = new CubicCurve2D.Float();
+	protected Shape edgeShape;
+	protected Shape rawArrowShape;
+	protected Shape arrowShape;
+	protected VisualizationServer.Paintable edgePaintable;
+	protected VisualizationServer.Paintable arrowPaintable;
+
+	
+	public CubicCurveEdgeEffects() {
+		this.rawEdge.setCurve(0.0f, 0.0f, 0.33f, 100, 0.66f, -50, 1.0f, 0.0f);
+		rawArrowShape = ArrowFactory.getNotchedArrow(20, 16, 8);
+		this.edgePaintable = new EdgePaintable();
+		this.arrowPaintable = new ArrowPaintable();
+	}
+
+//	@Override
+	public void startEdgeEffects(BasicVisualizationServer<V, E> vv,
+			Point2D down, Point2D out) {
+		transformEdgeShape(down, out);
+		vv.addPostRenderPaintable(edgePaintable);
+	}
+
+//	@Override
+	public void midEdgeEffects(BasicVisualizationServer<V, E> vv,
+			Point2D down, Point2D out) {
+		transformEdgeShape(down, out);
+	}
+
+//	@Override
+	public void endEdgeEffects(BasicVisualizationServer<V, E> vv) {
+		vv.removePostRenderPaintable(edgePaintable);
+	}
+
+//	@Override
+	public void startArrowEffects(BasicVisualizationServer<V, E> vv,
+			Point2D down, Point2D out) {
+		transformArrowShape(down, out);
+		vv.addPostRenderPaintable(arrowPaintable);
+	}
+
+//	@Override
+	public void midArrowEffects(BasicVisualizationServer<V, E> vv,
+			Point2D down, Point2D out) {
+		transformArrowShape(down, out);
+	}
+
+//	@Override
+	public void endArrowEffects(BasicVisualizationServer<V, E> vv) {
+		vv.removePostRenderPaintable(arrowPaintable);
+	}
+
+    /**
+     * code lifted from PluggableRenderer to move an edge shape into an
+     * arbitrary position
+     */
+    private void transformEdgeShape(Point2D down, Point2D out) {
+        float x1 = (float) down.getX();
+        float y1 = (float) down.getY();
+        float x2 = (float) out.getX();
+        float y2 = (float) out.getY();
+
+        AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
+        
+        float dx = x2-x1;
+        float dy = y2-y1;
+        float thetaRadians = (float) Math.atan2(dy, dx);
+        xform.rotate(thetaRadians);
+        float dist = (float) Math.sqrt(dx*dx + dy*dy);
+        xform.scale(dist / rawEdge.getBounds().getWidth(), 1.0);
+        edgeShape = xform.createTransformedShape(rawEdge);
+    }
+    
+    private void transformArrowShape(Point2D down, Point2D out) {
+        float x1 = (float) down.getX();
+        float y1 = (float) down.getY();
+        float x2 = (float) out.getX();
+        float y2 = (float) out.getY();
+
+        AffineTransform xform = AffineTransform.getTranslateInstance(x2, y2);
+        
+        float dx = x2-x1;
+        float dy = y2-y1;
+        float thetaRadians = (float) Math.atan2(dy, dx);
+        xform.rotate(thetaRadians);
+        arrowShape = xform.createTransformedShape(rawArrowShape);
+    }
+    /**
+     * Used for the edge creation visual effect during mouse drag
+     */
+    class EdgePaintable implements VisualizationServer.Paintable {
+        
+        public void paint(Graphics g) {
+            if(edgeShape != null) {
+                Color oldColor = g.getColor();
+                g.setColor(Color.black);
+                ((Graphics2D)g).draw(edgeShape);
+                g.setColor(oldColor);
+            }
+        }
+        
+        public boolean useTransform() {
+            return false;
+        }
+    }
+    
+    /**
+     * Used for the directed edge creation visual effect during mouse drag
+     */
+    class ArrowPaintable implements VisualizationServer.Paintable {
+        
+        public void paint(Graphics g) {
+            if(arrowShape != null) {
+                Color oldColor = g.getColor();
+                g.setColor(Color.black);
+                ((Graphics2D)g).fill(arrowShape);
+                g.setColor(oldColor);
+            }
+        }
+        
+        public boolean useTransform() {
+            return false;
+        }
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/DefaultModalGraphMouse.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/DefaultModalGraphMouse.java
new file mode 100644
index 0000000..91dfeec
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/DefaultModalGraphMouse.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.ItemSelectable;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+
+/** 
+ * 
+ * DefaultModalGraphMouse is a PluggableGraphMouse class that
+ * pre-installs a large collection of plugins for picking and
+ * transforming the graph. Additionally, it carries the notion
+ * of a Mode: Picking or Translating. Switching between modes
+ * allows for a more natural choice of mouse modifiers to
+ * be used for the various plugins. The default modifiers are
+ * intended to mimick those of mainstream software applications
+ * in order to be intuitive to users.
+ * 
+ * To change between modes, two different controls are offered,
+ * a combo box and a menu system. These controls are lazily created
+ * in their respective 'getter' methods so they don't impact
+ * code that does not intend to use them.
+ * The menu control can be placed in an unused corner of the
+ * GraphZoomScrollPane, which is a common location for mouse
+ * mode selection menus in mainstream applications.
+ * 
+ * @author Tom Nelson
+ */
+public class DefaultModalGraphMouse<V,E> extends AbstractModalGraphMouse 
+    implements ModalGraphMouse, ItemSelectable {
+    
+    /**
+     * create an instance with default values
+     *
+     */
+    public DefaultModalGraphMouse() {
+        this(1.1f, 1/1.1f);
+    }
+    
+    /**
+     * create an instance with passed values
+     * @param in override value for scale in
+     * @param out override value for scale out
+     */
+    public DefaultModalGraphMouse(float in, float out) {
+        super(in,out);
+        loadPlugins();
+		setModeKeyListener(new ModeKeyAdapter(this));
+    }
+    
+    /**
+     * create the plugins, and load the plugins for TRANSFORMING mode
+     *
+     */
+    @Override
+    protected void loadPlugins() {
+        pickingPlugin = new PickingGraphMousePlugin<V,E>();
+        animatedPickingPlugin = new AnimatedPickingGraphMousePlugin<V,E>();
+        translatingPlugin = new TranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK);
+        scalingPlugin = new ScalingGraphMousePlugin(new CrossoverScalingControl(), 0, in, out);
+        rotatingPlugin = new RotatingGraphMousePlugin();
+        shearingPlugin = new ShearingGraphMousePlugin();
+
+        add(scalingPlugin);
+        setMode(Mode.TRANSFORMING);
+    }
+    
+    public static class ModeKeyAdapter extends KeyAdapter {
+    	private char t = 't';
+    	private char p = 'p';
+    	protected ModalGraphMouse graphMouse;
+
+    	public ModeKeyAdapter(ModalGraphMouse graphMouse) {
+			this.graphMouse = graphMouse;
+		}
+
+		public ModeKeyAdapter(char t, char p, ModalGraphMouse graphMouse) {
+			this.t = t;
+			this.p = p;
+			this.graphMouse = graphMouse;
+		}
+		
+		@Override
+        public void keyTyped(KeyEvent event) {
+			char keyChar = event.getKeyChar();
+			if(keyChar == t) {
+				((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+				graphMouse.setMode(Mode.TRANSFORMING);
+			} else if(keyChar == p) {
+				((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+				graphMouse.setMode(Mode.PICKING);
+			}
+		}
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EdgeEffects.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EdgeEffects.java
new file mode 100644
index 0000000..c1cf61a
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EdgeEffects.java
@@ -0,0 +1,25 @@
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.BasicVisualizationServer;
+
+public interface EdgeEffects<V,E> {
+	
+	void startEdgeEffects(BasicVisualizationServer<V,E> vv,
+			Point2D down, Point2D out);
+	
+	void midEdgeEffects(BasicVisualizationServer<V,E> vv,
+			Point2D down, Point2D out);
+	
+	void endEdgeEffects(BasicVisualizationServer<V,E> vv);
+	
+	void startArrowEffects(BasicVisualizationServer<V,E> vv,
+			Point2D down, Point2D out);
+	
+	void midArrowEffects(BasicVisualizationServer<V,E> vv,
+			Point2D down, Point2D out);
+
+	void endArrowEffects(BasicVisualizationServer<V,E> vv);
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EdgeSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EdgeSupport.java
new file mode 100644
index 0000000..fa37913
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EdgeSupport.java
@@ -0,0 +1,25 @@
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.BasicVisualizationServer;
+
+/**
+ * interface to support the creation of new edges by the EditingGraphMousePlugin
+ * SimpleEdgeSupport is a sample implementation
+ * @author tanelso
+ *
+ * @param <V> the vertex type
+ * @param <V> the edge type
+ */
+public interface EdgeSupport<V,E> {
+	
+	void startEdgeCreate(BasicVisualizationServer<V,E> vv, V startVertex, 
+			Point2D startPoint, EdgeType edgeType);
+	
+	void midEdgeCreate(BasicVisualizationServer<V,E> vv, Point2D midPoint);
+	
+	void endEdgeCreate(BasicVisualizationServer<V,E> vv, V endVertex);
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EditingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EditingGraphMousePlugin.java
new file mode 100644
index 0000000..24bda9d
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EditingGraphMousePlugin.java
@@ -0,0 +1,178 @@
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Cursor;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+
+import javax.swing.JComponent;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+
+/**
+ * A plugin that can create vertices, undirected edges, and directed edges
+ * using mouse gestures.
+ * 
+ * vertexSupport and edgeSupport member classes are responsible for actually
+ * creating the new graph elements, and for repainting the view when changes
+ * were made.
+ * 
+ * @author Tom Nelson
+ *
+ */
+public class EditingGraphMousePlugin<V,E> extends AbstractGraphMousePlugin implements
+    MouseListener, MouseMotionListener {
+    
+	protected VertexSupport<V,E> vertexSupport;
+	protected EdgeSupport<V,E> edgeSupport;
+	private Creating createMode = Creating.UNDETERMINED;
+	private enum Creating { EDGE, VERTEX, UNDETERMINED }
+    
+    /**
+     * Creates an instance and prepares shapes for visual effects, using the default modifiers
+     * of BUTTON1_MASK.
+     * @param vertexFactory for creating vertices
+     * @param edgeFactory for creating edges
+     */
+    public EditingGraphMousePlugin(Supplier<V> vertexFactory, Supplier<E> edgeFactory) {
+        this(MouseEvent.BUTTON1_MASK, vertexFactory, edgeFactory);
+    }
+
+    /**
+     * Creates an instance and prepares shapes for visual effects.
+     * @param modifiers the mouse event modifiers to use
+     * @param vertexFactory for creating vertices
+     * @param edgeFactory for creating edges
+     */
+    public EditingGraphMousePlugin(int modifiers, Supplier<V> vertexFactory, Supplier<E> edgeFactory) {
+        super(modifiers);
+		this.cursor = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
+		this.vertexSupport = new SimpleVertexSupport<V,E>(vertexFactory);
+		this.edgeSupport = new SimpleEdgeSupport<V,E>(edgeFactory);
+    }
+    
+    /**
+     * Overridden to be more flexible, and pass events with
+     * key combinations. The default responds to both ButtonOne
+     * and ButtonOne+Shift
+     */
+    @Override
+    public boolean checkModifiers(MouseEvent e) {
+        return (e.getModifiers() & modifiers) != 0;
+    }
+
+    /**
+     * If the mouse is pressed in an empty area, create a new vertex there.
+     * If the mouse is pressed on an existing vertex, prepare to create
+     * an edge from that vertex to another
+     */
+    @SuppressWarnings("unchecked")
+	public void mousePressed(MouseEvent e) {
+        if(checkModifiers(e)) {
+            final VisualizationViewer<V,E> vv =
+                (VisualizationViewer<V,E>)e.getSource();
+            final Point2D p = e.getPoint();
+            GraphElementAccessor<V,E> pickSupport = vv.getPickSupport();
+            if(pickSupport != null) {
+                final V vertex = pickSupport.getVertex(vv.getModel().getGraphLayout(), p.getX(), p.getY());
+                if(vertex != null) { // get ready to make an edge
+                	this.createMode = Creating.EDGE;
+                	Graph<V,E> graph = vv.getModel().getGraphLayout().getGraph();
+                	// set default edge type
+                	EdgeType edgeType = (graph instanceof DirectedGraph) ?
+                			EdgeType.DIRECTED : EdgeType.UNDIRECTED;
+                    if((e.getModifiers() & MouseEvent.SHIFT_MASK) != 0
+                    		&& graph instanceof UndirectedGraph == false) {
+                        edgeType = EdgeType.DIRECTED;
+                    }
+                    edgeSupport.startEdgeCreate(vv, vertex, e.getPoint(), edgeType);
+                } else { // make a new vertex
+                	this.createMode = Creating.VERTEX;
+                	vertexSupport.startVertexCreate(vv, e.getPoint());
+                }
+            }
+        }
+    }
+    
+    /**
+     * If startVertex is non-null, and the mouse is released over an
+     * existing vertex, create an undirected edge from startVertex to
+     * the vertex under the mouse pointer. If shift was also pressed,
+     * create a directed edge instead.
+     */
+    @SuppressWarnings("unchecked")
+	public void mouseReleased(MouseEvent e) {
+        if(checkModifiers(e)) {
+            final VisualizationViewer<V,E> vv =
+                (VisualizationViewer<V,E>)e.getSource();
+            final Point2D p = e.getPoint();
+            Layout<V,E> layout = vv.getGraphLayout();
+            if(createMode == Creating.EDGE) {
+                GraphElementAccessor<V,E> pickSupport = vv.getPickSupport();
+                V vertex = null;
+                if(pickSupport != null) {
+                    vertex = pickSupport.getVertex(layout, p.getX(), p.getY());
+                }
+                edgeSupport.endEdgeCreate(vv, vertex);
+            } else if(createMode == Creating.VERTEX){
+            	vertexSupport.endVertexCreate(vv, e.getPoint());
+            }
+        }
+        createMode = Creating.UNDETERMINED;
+    }
+
+    /**
+     * If startVertex is non-null, stretch an edge shape between
+     * startVertex and the mouse pointer to simulate edge creation
+     */
+    @SuppressWarnings("unchecked")
+    public void mouseDragged(MouseEvent e) {
+        if(checkModifiers(e)) {
+            VisualizationViewer<V,E> vv =
+                (VisualizationViewer<V,E>)e.getSource();
+            if(createMode == Creating.EDGE) {
+            	edgeSupport.midEdgeCreate(vv, e.getPoint());
+            } else if(createMode == Creating.VERTEX){
+            	vertexSupport.midVertexCreate(vv, e.getPoint());
+            }
+        }
+    }
+    
+    public void mouseClicked(MouseEvent e) {}
+    public void mouseEntered(MouseEvent e) {
+        JComponent c = (JComponent)e.getSource();
+        c.setCursor(cursor);
+    }
+    public void mouseExited(MouseEvent e) {
+        JComponent c = (JComponent)e.getSource();
+        c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    }
+    public void mouseMoved(MouseEvent e) {}
+
+	public VertexSupport<V,E> getVertexSupport() {
+		return vertexSupport;
+	}
+
+	public void setVertexSupport(VertexSupport<V,E> vertexSupport) {
+		this.vertexSupport = vertexSupport;
+	}
+
+	public EdgeSupport<V,E> getEdgeSupport() {
+		return edgeSupport;
+	}
+
+	public void setEdgeSupport(EdgeSupport<V,E> edgeSupport) {
+		this.edgeSupport = edgeSupport;
+	}
+    
+    
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EditingModalGraphMouse.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EditingModalGraphMouse.java
new file mode 100644
index 0000000..2c7c0b4
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EditingModalGraphMouse.java
@@ -0,0 +1,312 @@
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.ItemSelectable;
+import java.awt.event.InputEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import javax.swing.ButtonGroup;
+import javax.swing.Icon;
+import javax.swing.JComboBox;
+import javax.swing.JMenu;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.plaf.basic.BasicIconFactory;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.visualization.MultiLayerTransformer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.annotations.AnnotatingGraphMousePlugin;
+
+public class EditingModalGraphMouse<V,E> extends AbstractModalGraphMouse 
+	implements ModalGraphMouse, ItemSelectable {
+
+	protected Supplier<V> vertexFactory;
+	protected Supplier<E> edgeFactory;
+	protected EditingGraphMousePlugin<V,E> editingPlugin;
+	protected LabelEditingGraphMousePlugin<V,E> labelEditingPlugin;
+	protected EditingPopupGraphMousePlugin<V,E> popupEditingPlugin;
+	protected AnnotatingGraphMousePlugin<V,E> annotatingPlugin;
+	protected MultiLayerTransformer basicTransformer;
+	protected RenderContext<V,E> rc;
+
+	/**
+	 * Creates an instance with the specified rendering context and vertex/edge factories,
+	 * and with default zoom in/out values of 1.1 and 1/1.1.
+	 * @param rc the rendering context
+	 * @param vertexFactory used to construct vertices
+	 * @param edgeFactory used to construct edges
+	 */
+	public EditingModalGraphMouse(RenderContext<V,E> rc,
+			Supplier<V> vertexFactory, Supplier<E> edgeFactory) {
+		this(rc, vertexFactory, edgeFactory, 1.1f, 1/1.1f);
+	}
+
+	/**
+	 * Creates an instance with the specified rendering context and vertex/edge factories,
+	 * and with the specified zoom in/out values.
+	 * @param rc the rendering context
+	 * @param vertexFactory used to construct vertices
+	 * @param edgeFactory used to construct edges
+	 * @param in amount to zoom in by for each action
+	 * @param out amount to zoom out by for each action
+	 */
+	public EditingModalGraphMouse(RenderContext<V,E> rc,
+			Supplier<V> vertexFactory, Supplier<E> edgeFactory, float in, float out) {
+		super(in,out);
+		this.vertexFactory = vertexFactory;
+		this.edgeFactory = edgeFactory;
+		this.rc = rc;
+		this.basicTransformer = rc.getMultiLayerTransformer();
+		loadPlugins();
+		setModeKeyListener(new ModeKeyAdapter(this));
+	}
+
+	/**
+	 * create the plugins, and load the plugins for TRANSFORMING mode
+	 *
+	 */
+	@Override
+    protected void loadPlugins() {
+		pickingPlugin = new PickingGraphMousePlugin<V,E>();
+		animatedPickingPlugin = new AnimatedPickingGraphMousePlugin<V,E>();
+		translatingPlugin = new TranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK);
+		scalingPlugin = new ScalingGraphMousePlugin(new CrossoverScalingControl(), 0, in, out);
+		rotatingPlugin = new RotatingGraphMousePlugin();
+		shearingPlugin = new ShearingGraphMousePlugin();
+		editingPlugin = new EditingGraphMousePlugin<V,E>(vertexFactory, edgeFactory);
+		labelEditingPlugin = new LabelEditingGraphMousePlugin<V,E>();
+		annotatingPlugin = new AnnotatingGraphMousePlugin<V,E>(rc);
+		popupEditingPlugin = new EditingPopupGraphMousePlugin<V,E>(vertexFactory, edgeFactory);
+		add(scalingPlugin);
+		setMode(Mode.EDITING);
+	}
+
+	/**
+	 * setter for the Mode.
+	 */
+	@Override
+    public void setMode(Mode mode) {
+		if(this.mode != mode) {
+			fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED,
+					this.mode, ItemEvent.DESELECTED));
+			this.mode = mode;
+			if(mode == Mode.TRANSFORMING) {
+				setTransformingMode();
+			} else if(mode == Mode.PICKING) {
+				setPickingMode();
+			} else if(mode == Mode.EDITING) {
+				setEditingMode();
+			} else if(mode == Mode.ANNOTATING) {
+				setAnnotatingMode();
+			}
+			if(modeBox != null) {
+				modeBox.setSelectedItem(mode);
+			}
+			fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, mode, ItemEvent.SELECTED));
+		}
+	}
+	
+	@Override
+    protected void setPickingMode() {
+		remove(translatingPlugin);
+		remove(rotatingPlugin);
+		remove(shearingPlugin);
+		remove(editingPlugin);
+		remove(annotatingPlugin);
+		add(pickingPlugin);
+		add(animatedPickingPlugin);
+		add(labelEditingPlugin);
+		add(popupEditingPlugin);
+	}
+
+	@Override
+    protected void setTransformingMode() {
+		remove(pickingPlugin);
+		remove(animatedPickingPlugin);
+		remove(editingPlugin);
+		remove(annotatingPlugin);
+		add(translatingPlugin);
+		add(rotatingPlugin);
+		add(shearingPlugin);
+		add(labelEditingPlugin);
+		add(popupEditingPlugin);
+	}
+
+	protected void setEditingMode() {
+		remove(pickingPlugin);
+		remove(animatedPickingPlugin);
+		remove(translatingPlugin);
+		remove(rotatingPlugin);
+		remove(shearingPlugin);
+		remove(labelEditingPlugin);
+		remove(annotatingPlugin);
+		add(editingPlugin);
+		add(popupEditingPlugin);
+	}
+
+	protected void setAnnotatingMode() {
+		remove(pickingPlugin);
+		remove(animatedPickingPlugin);
+		remove(translatingPlugin);
+		remove(rotatingPlugin);
+		remove(shearingPlugin);
+		remove(labelEditingPlugin);
+		remove(editingPlugin);
+		remove(popupEditingPlugin);
+		add(annotatingPlugin);
+	}
+
+
+	/**
+	 * @return the modeBox.
+	 */
+	@Override
+    public JComboBox<Mode> getModeComboBox() {
+		if(modeBox == null) {
+			modeBox = new JComboBox<Mode>(
+				new Mode[]{Mode.TRANSFORMING, Mode.PICKING, Mode.EDITING, Mode.ANNOTATING});
+			modeBox.addItemListener(getModeListener());
+		}
+		modeBox.setSelectedItem(mode);
+		return modeBox;
+	}
+
+	/**
+	 * create (if necessary) and return a menu that will change
+	 * the mode
+	 * @return the menu
+	 */
+	@Override
+    public JMenu getModeMenu() {
+		if(modeMenu == null) {
+			modeMenu = new JMenu();// {
+			Icon icon = BasicIconFactory.getMenuArrowIcon();
+			modeMenu.setIcon(BasicIconFactory.getMenuArrowIcon());
+			modeMenu.setPreferredSize(new Dimension(icon.getIconWidth()+10, 
+					icon.getIconHeight()+10));
+
+			final JRadioButtonMenuItem transformingButton = 
+				new JRadioButtonMenuItem(Mode.TRANSFORMING.toString());
+			transformingButton.addItemListener(new ItemListener() {
+				public void itemStateChanged(ItemEvent e) {
+					if(e.getStateChange() == ItemEvent.SELECTED) {
+						setMode(Mode.TRANSFORMING);
+					}
+				}});
+
+			final JRadioButtonMenuItem pickingButton =
+				new JRadioButtonMenuItem(Mode.PICKING.toString());
+			pickingButton.addItemListener(new ItemListener() {
+				public void itemStateChanged(ItemEvent e) {
+					if(e.getStateChange() == ItemEvent.SELECTED) {
+						setMode(Mode.PICKING);
+					}
+				}});
+
+			final JRadioButtonMenuItem editingButton =
+				new JRadioButtonMenuItem(Mode.EDITING.toString());
+			editingButton.addItemListener(new ItemListener() {
+				public void itemStateChanged(ItemEvent e) {
+					if(e.getStateChange() == ItemEvent.SELECTED) {
+						setMode(Mode.EDITING);
+					}
+				}});
+
+			ButtonGroup radio = new ButtonGroup();
+			radio.add(transformingButton);
+			radio.add(pickingButton);
+			radio.add(editingButton);
+			transformingButton.setSelected(true);
+			modeMenu.add(transformingButton);
+			modeMenu.add(pickingButton);
+			modeMenu.add(editingButton);
+			modeMenu.setToolTipText("Menu for setting Mouse Mode");
+			addItemListener(new ItemListener() {
+				public void itemStateChanged(ItemEvent e) {
+					if(e.getStateChange() == ItemEvent.SELECTED) {
+						if(e.getItem() == Mode.TRANSFORMING) {
+							transformingButton.setSelected(true);
+						} else if(e.getItem() == Mode.PICKING) {
+							pickingButton.setSelected(true);
+						} else if(e.getItem() == Mode.EDITING) {
+							editingButton.setSelected(true);
+						}
+					}
+				}});
+		}
+		return modeMenu;
+	}
+	
+    public static class ModeKeyAdapter extends KeyAdapter {
+    	private char t = 't';
+    	private char p = 'p';
+    	private char e = 'e';
+    	private char a = 'a';
+    	protected ModalGraphMouse graphMouse;
+
+    	public ModeKeyAdapter(ModalGraphMouse graphMouse) {
+			this.graphMouse = graphMouse;
+		}
+
+		public ModeKeyAdapter(char t, char p, char e, char a, ModalGraphMouse graphMouse) {
+			this.t = t;
+			this.p = p;
+			this.e = e;
+			this.a = a;
+			this.graphMouse = graphMouse;
+		}
+		
+		@Override
+        public void keyTyped(KeyEvent event) {
+			char keyChar = event.getKeyChar();
+			if(keyChar == t) {
+				((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+				graphMouse.setMode(Mode.TRANSFORMING);
+			} else if(keyChar == p) {
+				((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+				graphMouse.setMode(Mode.PICKING);
+			} else if(keyChar == e) {
+				((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
+				graphMouse.setMode(Mode.EDITING);
+			} else if(keyChar == a) {
+				((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
+				graphMouse.setMode(Mode.ANNOTATING);
+			}
+		}
+    }
+
+	/**
+	 * @return the annotatingPlugin
+	 */
+	public AnnotatingGraphMousePlugin<V, E> getAnnotatingPlugin() {
+		return annotatingPlugin;
+	}
+
+	/**
+	 * @return the editingPlugin
+	 */
+	public EditingGraphMousePlugin<V, E> getEditingPlugin() {
+		return editingPlugin;
+	}
+
+	/**
+	 * @return the labelEditingPlugin
+	 */
+	public LabelEditingGraphMousePlugin<V, E> getLabelEditingPlugin() {
+		return labelEditingPlugin;
+	}
+
+	/**
+	 * @return the popupEditingPlugin
+	 */
+	public EditingPopupGraphMousePlugin<V, E> getPopupEditingPlugin() {
+		return popupEditingPlugin;
+	}
+}
+
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EditingPopupGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EditingPopupGraphMousePlugin.java
new file mode 100644
index 0000000..52d967f
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/EditingPopupGraphMousePlugin.java
@@ -0,0 +1,115 @@
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Point2D;
+import java.util.Set;
+
+import javax.swing.AbstractAction;
+import javax.swing.JMenu;
+import javax.swing.JPopupMenu;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.DirectedGraph;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.UndirectedGraph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+
+/**
+ * a plugin that uses popup menus to create vertices, undirected edges,
+ * and directed edges.
+ * 
+ * @author Tom Nelson
+ *
+ */
+public class EditingPopupGraphMousePlugin<V,E> extends AbstractPopupGraphMousePlugin {
+    
+    protected Supplier<V> vertexFactory;
+    protected Supplier<E> edgeFactory;
+
+    public EditingPopupGraphMousePlugin(Supplier<V> vertexFactory, Supplier<E> edgeFactory) {
+        this.vertexFactory = vertexFactory;
+        this.edgeFactory = edgeFactory;
+    }
+    
+	@SuppressWarnings({ "unchecked", "serial" })
+	protected void handlePopup(MouseEvent e) {
+        final VisualizationViewer<V,E> vv =
+            (VisualizationViewer<V,E>)e.getSource();
+        final Layout<V,E> layout = vv.getGraphLayout();
+        final Graph<V,E> graph = layout.getGraph();
+        final Point2D p = e.getPoint();
+        GraphElementAccessor<V,E> pickSupport = vv.getPickSupport();
+        if(pickSupport != null) {
+            
+            final V vertex = pickSupport.getVertex(layout, p.getX(), p.getY());
+            final E edge = pickSupport.getEdge(layout, p.getX(), p.getY());
+            final PickedState<V> pickedVertexState = vv.getPickedVertexState();
+            final PickedState<E> pickedEdgeState = vv.getPickedEdgeState();
+            
+            JPopupMenu popup = new JPopupMenu();
+            if(vertex != null) {
+            	Set<V> picked = pickedVertexState.getPicked();
+            	if(picked.size() > 0) {
+            		if(graph instanceof UndirectedGraph == false) {
+            			JMenu directedMenu = new JMenu("Create Directed Edge");
+            			popup.add(directedMenu);
+            			for(final V other : picked) {
+            				directedMenu.add(new AbstractAction("["+other+","+vertex+"]") {
+            					public void actionPerformed(ActionEvent e) {
+            						graph.addEdge(edgeFactory.get(),
+            								other, vertex, EdgeType.DIRECTED);
+            						vv.repaint();
+            					}
+            				});
+            			}
+            		}
+            		if(graph instanceof DirectedGraph == false) {
+            			JMenu undirectedMenu = new JMenu("Create Undirected Edge");
+            			popup.add(undirectedMenu);
+            			for(final V other : picked) {
+            				undirectedMenu.add(new AbstractAction("[" + other+","+vertex+"]") {
+            					public void actionPerformed(ActionEvent e) {
+            						graph.addEdge(edgeFactory.get(),
+            								other, vertex);
+            						vv.repaint();
+            					}
+            				});
+            			}
+            		}
+                }
+                popup.add(new AbstractAction("Delete Vertex") {
+                    public void actionPerformed(ActionEvent e) {
+                        pickedVertexState.pick(vertex, false);
+                        graph.removeVertex(vertex);
+                        vv.repaint();
+                    }});
+            } else if(edge != null) {
+                popup.add(new AbstractAction("Delete Edge") {
+                    public void actionPerformed(ActionEvent e) {
+                        pickedEdgeState.pick(edge, false);
+                        graph.removeEdge(edge);
+                        vv.repaint();
+                    }});
+            } else {
+                popup.add(new AbstractAction("Create Vertex") {
+                    public void actionPerformed(ActionEvent e) {
+                        V newVertex = vertexFactory.get();
+                        graph.addVertex(newVertex);
+                        layout.setLocation(newVertex, vv.getRenderContext().getMultiLayerTransformer().inverseTransform(p));
+                        vv.repaint();
+                    }
+                });
+            }
+            if(popup.getComponentCount() > 0) {
+                popup.show(vv, e.getX(), e.getY());
+            }
+        }
+    }
+}
+
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/GraphMouseAdapter.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/GraphMouseAdapter.java
new file mode 100644
index 0000000..82c1b40
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/GraphMouseAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 6, 2005
+ */
+
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+/**
+ * Simple extension of MouseAdapter that supplies modifier
+ * checking
+ * 
+ * @author Tom Nelson 
+ *
+ */
+public class GraphMouseAdapter extends MouseAdapter {
+
+    protected int modifiers;
+    
+    public GraphMouseAdapter(int modifiers) {
+        this.modifiers = modifiers;
+    }
+    
+    public int getModifiers() {
+        return modifiers;
+    }
+
+    public void setModifiers(int modifiers) {
+        this.modifiers = modifiers;
+    }
+    
+    protected boolean checkModifiers(MouseEvent e) {
+        return e.getModifiers() == modifiers;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/GraphMouseListener.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/GraphMouseListener.java
new file mode 100644
index 0000000..7d1cbc8
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/GraphMouseListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Feb 17, 2004
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.MouseEvent;
+
+/**
+ * This interface allows users to register listeners to register to receive
+ * vertex clicks.
+ * 
+ * @author danyelf
+ */
+public interface GraphMouseListener<V> {
+
+	void graphClicked(V v, MouseEvent me);
+	void graphPressed(V v, MouseEvent me);
+	void graphReleased(V v, MouseEvent me);
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/GraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/GraphMousePlugin.java
new file mode 100644
index 0000000..0cdb281
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/GraphMousePlugin.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 6, 2005
+ */
+
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.MouseEvent;
+
+/**
+ * the interface for all plugins to the PluggableGraphMouse
+ * @author Tom Nelson 
+ *
+ */
+public interface GraphMousePlugin {
+
+	/**
+	 * @return the mouse event modifiers that will activate this plugin
+	 */
+    int getModifiers();
+
+    /**
+     * @param modifiers the mouse event modifiers that will activate this plugin
+     */
+    void setModifiers(int modifiers);
+    
+    /**
+     * compare the set modifiers against those of the supplied event
+     * @param e an event to compare to
+     * @return whether the member modifiers match the event modifiers
+     */
+    boolean checkModifiers(MouseEvent e);
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/LabelEditingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/LabelEditingGraphMousePlugin.java
new file mode 100644
index 0000000..46d2746
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/LabelEditingGraphMousePlugin.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Cursor;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.geom.Point2D;
+
+import javax.swing.JOptionPane;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.util.MapSettableTransformer;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+
+/** 
+ * 
+ * 
+ * @author Tom Nelson
+ */
+public class LabelEditingGraphMousePlugin<V, E> extends AbstractGraphMousePlugin
+    implements MouseListener {
+
+	/**
+	 * the picked Vertex, if any
+	 */
+    protected V vertex;
+    
+    /**
+     * the picked Edge, if any
+     */
+    protected E edge;
+    
+    /**
+	 * create an instance with default settings
+	 */
+	public LabelEditingGraphMousePlugin() {
+	    this(InputEvent.BUTTON1_MASK);
+	}
+
+	/**
+	 * create an instance with overrides
+	 * @param selectionModifiers for primary selection
+	 */
+    public LabelEditingGraphMousePlugin(int selectionModifiers) {
+        super(selectionModifiers);
+        this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+    }
+    
+	/**
+	 * For primary modifiers (default, MouseButton1):
+	 * pick a single Vertex or Edge that
+     * is under the mouse pointer. If no Vertex or edge is under
+     * the pointer, unselect all picked Vertices and edges, and
+     * set up to draw a rectangle for multiple selection
+     * of contained Vertices.
+     * For additional selection (default Shift+MouseButton1):
+     * Add to the selection, a single Vertex or Edge that is
+     * under the mouse pointer. If a previously picked Vertex
+     * or Edge is under the pointer, it is un-picked.
+     * If no vertex or Edge is under the pointer, set up
+     * to draw a multiple selection rectangle (as above)
+     * but do not unpick previously picked elements.
+	 * 
+	 * @param e the event
+	 */
+    @SuppressWarnings("unchecked")
+    public void mouseClicked(MouseEvent e) {
+    	if (e.getModifiers() == modifiers && e.getClickCount() == 2) {
+    		VisualizationViewer<V,E> vv = (VisualizationViewer<V, E>)e.getSource();
+    		GraphElementAccessor<V,E> pickSupport = vv.getPickSupport();
+    		if (pickSupport != null) {
+    			Function<? super V,String> vs = vv.getRenderContext().getVertexLabelTransformer();
+    			if (vs instanceof MapSettableTransformer) {
+    				MapSettableTransformer<? super V, String> mst =
+    					(MapSettableTransformer<? super V, String>)vs;
+    				Layout<V,E> layout = vv.getGraphLayout();
+    				// p is the screen point for the mouse event
+    				Point2D p = e.getPoint();
+
+    				V vertex = pickSupport.getVertex(layout, p.getX(), p.getY());
+    				if(vertex != null) {
+    					String newLabel = vs.apply(vertex);
+    					newLabel = JOptionPane.showInputDialog("New Vertex Label for "+vertex);
+    					if(newLabel != null) {
+    						mst.set(vertex, newLabel);
+    						vv.repaint();
+    					}
+    					return;
+    				}
+    			}
+    			Function<? super E,String> es = vv.getRenderContext().getEdgeLabelTransformer();
+    			if (es instanceof MapSettableTransformer) {
+    				MapSettableTransformer<? super E, String> mst =
+    					(MapSettableTransformer<? super E, String>)es;
+    				Layout<V,E> layout = vv.getGraphLayout();
+    				// p is the screen point for the mouse event
+    				Point2D p = e.getPoint();
+    				// take away the view transform
+    				Point2D ip = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, p);
+    				E edge = pickSupport.getEdge(layout, ip.getX(), ip.getY());
+    				if(edge != null) {
+    					String newLabel = JOptionPane.showInputDialog("New Edge Label for "+edge);
+    					if(newLabel != null) {
+    						mst.set(edge, newLabel);
+    						vv.repaint();
+    					}
+    					return;
+    				}
+    			}
+    		}
+     		e.consume();
+     	}
+    }
+
+    /**
+	 * If the mouse is dragging a rectangle, pick the
+	 * Vertices contained in that rectangle
+	 * 
+	 * clean up settings from mousePressed
+	 */
+    public void mouseReleased(MouseEvent e) {
+    }
+    
+    /**
+	 * If the mouse is over a picked vertex, drag all picked
+	 * vertices with the mouse.
+	 * If the mouse is not over a Vertex, draw the rectangle
+	 * to select multiple Vertices
+	 * 
+	 */
+
+    public void mousePressed(MouseEvent e) {
+    }
+
+	public void mouseEntered(MouseEvent e) {
+	}
+
+	public void mouseExited(MouseEvent e) {
+	}
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/LayoutScalingControl.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/LayoutScalingControl.java
new file mode 100644
index 0000000..aa92d7f
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/LayoutScalingControl.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/** 
+ * LayoutScalingControl applies a scaling transformation to the graph layout.
+ * The Vertices get closer or farther apart, but do not themselves change
+ * size. ScalingGraphMouse uses MouseWheelEvents to apply the scaling.
+ * 
+ * @author Tom Nelson
+ */
+public class LayoutScalingControl implements ScalingControl {
+
+    /**
+	 * zoom the display in or out, depending on the direction of the
+	 * mouse wheel motion.
+	 */
+    public void scale(VisualizationServer<?, ?> vv, float amount, Point2D from) {
+        
+        Point2D ivtfrom = vv.getRenderContext().getMultiLayerTransformer()
+        	.inverseTransform(Layer.VIEW, from);
+        MutableTransformer modelTransformer = vv.getRenderContext().getMultiLayerTransformer()
+        	.getTransformer(Layer.LAYOUT);
+        modelTransformer.scale(amount, amount, ivtfrom);
+        vv.repaint();
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/LensMagnificationGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/LensMagnificationGraphMousePlugin.java
new file mode 100644
index 0000000..43d6889
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/LensMagnificationGraphMousePlugin.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.transform.LensTransformer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/** 
+ * HyperbolicMagnificationGraphMousePlugin changes the magnification
+ * within the Hyperbolic projection of the HyperbolicTransformer.
+ * 
+ * @author Tom Nelson
+ */
+public class LensMagnificationGraphMousePlugin extends AbstractGraphMousePlugin
+    implements MouseWheelListener {
+
+    protected final float floor;
+    protected final float ceiling;
+    protected final float delta;
+    
+	/**
+	 * Creates an instance with modifier of CTRL_MASK, and default min/max/delta zoom values
+	 * of 1/4/0.2.
+	 */
+	public LensMagnificationGraphMousePlugin() {
+	    this(MouseEvent.CTRL_MASK);
+	}
+    
+	/**
+	 * Creates an instance with modifier of CTRL_MASK, and the specified zoom parameters.
+	 * @param floor the minimum zoom value
+	 * @param ceiling the maximum zoom value
+	 * @param delta the change in zoom value caused by each mouse event
+	 */
+    public LensMagnificationGraphMousePlugin(float floor, float ceiling, float delta) {
+        this(MouseEvent.CTRL_MASK, floor, ceiling, delta);
+    }
+    
+    /**
+     * Creates an instance with the specified modifiers and the default min/max/delta zoom values
+     * of 1/4/0.2.
+     * @param modifiers the mouse event modifiers to specify
+     */
+    public LensMagnificationGraphMousePlugin(int modifiers) {
+        this(modifiers, 1.0f, 4.0f, .2f);
+    }
+    
+	/**
+	 * Creates an instance with the specified mouse event modifiers and zoom parameters.
+     * @param modifiers the mouse event modifiers to specify
+	 * @param floor the minimum zoom value
+	 * @param ceiling the maximum zoom value
+	 * @param delta the change in zoom value caused by each mouse event
+	 */
+    public LensMagnificationGraphMousePlugin(int modifiers, float floor, float ceiling, float delta) {
+        super(modifiers);
+        this.floor = floor;
+        this.ceiling = ceiling;
+        this.delta = delta;
+    }
+
+    /**
+     * override to check equality with a mask
+     */
+    public boolean checkModifiers(MouseEvent e) {
+        return (e.getModifiers() & modifiers) != 0;
+    }
+
+    private void changeMagnification(MutableTransformer transformer, float delta) {
+        if(transformer instanceof LensTransformer) {
+            LensTransformer ht = (LensTransformer)transformer;
+            float magnification = ht.getMagnification() + delta;
+            magnification = Math.max(floor, magnification);
+            magnification = Math.min(magnification, ceiling);
+            ht.setMagnification(magnification);
+        }
+    }
+	/**
+	 * zoom the display in or out, depending on the direction of the
+	 * mouse wheel motion.
+	 */
+    public void mouseWheelMoved(MouseWheelEvent e) {
+        boolean accepted = checkModifiers(e);
+        float delta = this.delta;
+        if(accepted == true) {
+            VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+            MutableTransformer modelTransformer = vv.getRenderContext().getMultiLayerTransformer()
+            	.getTransformer(Layer.LAYOUT);
+            MutableTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer()
+            	.getTransformer(Layer.VIEW);
+            int amount = e.getWheelRotation();
+            if(amount < 0) {
+                delta = -delta;
+            }
+            changeMagnification(modelTransformer, delta);
+            changeMagnification(viewTransformer, delta);
+            vv.repaint();
+            e.consume();
+        }
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/LensTranslatingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/LensTranslatingGraphMousePlugin.java
new file mode 100644
index 0000000..83d202f
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/LensTranslatingGraphMousePlugin.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Cursor;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.transform.LensTransformer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/** 
+ * Extends TranslatingGraphMousePlugin and adds the capability 
+ * to drag and resize the viewing
+ * lens in the graph view. Mouse1 in the center moves the lens,
+ * mouse1 on the edge resizes the lens. The default mouse button and
+ * modifiers can be overridden in the constructor.
+ * 
+ * 
+ * @author Tom Nelson
+ */
+public class LensTranslatingGraphMousePlugin extends TranslatingGraphMousePlugin
+implements MouseListener, MouseMotionListener {
+    
+    protected boolean dragOnLens;
+    protected boolean dragOnEdge;
+    protected double edgeOffset;
+    /**
+     * create an instance with default modifiers
+     */
+    public LensTranslatingGraphMousePlugin() {
+        this(MouseEvent.BUTTON1_MASK);
+    }
+    
+    /**
+     * create an instance with passed modifer value
+     * @param modifiers the mouse event modifier to activate this function
+     */
+    public LensTranslatingGraphMousePlugin(int modifiers) {
+        super(modifiers);
+    }
+    
+    /**
+     * Check the event modifiers. Set the 'down' point for later
+     * use. If this event satisfies the modifiers, change the cursor
+     * to the system 'move cursor'
+     * @param e the event
+     */
+    public void mousePressed(MouseEvent e) {
+        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+        MutableTransformer vt = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
+        if(vt instanceof LensTransformer) {
+        	vt = ((LensTransformer)vt).getDelegate();
+        }
+        Point2D p = vt.inverseTransform(e.getPoint());
+        boolean accepted = checkModifiers(e);
+        if(accepted) {
+            vv.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+            testViewCenter(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT), p);
+            testViewCenter(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW), p);
+            vv.repaint();
+        }
+        super.mousePressed(e);
+    }
+    
+    /**
+     * called to change the location of the lens
+     * @param Function
+     * @param point
+     */
+    private void setViewCenter(MutableTransformer transformer, Point2D point) {
+        if(transformer instanceof LensTransformer) {
+            LensTransformer ht =
+                (LensTransformer)transformer;
+            ht.setViewCenter(point);
+        }
+    }
+    
+    /**
+     * called to change the radius of the lens
+     * @param Function
+     * @param point
+     */
+    private void setViewRadius(MutableTransformer transformer, Point2D point) {
+        if(transformer instanceof LensTransformer) {
+            LensTransformer ht =
+                (LensTransformer)transformer;
+            double distanceFromCenter = ht.getDistanceFromCenter(point);
+            ht.setViewRadius(distanceFromCenter+edgeOffset);
+        }
+    }
+    
+    /**
+     * called to set up translating the lens center or changing the size
+     * @param Function
+     * @param point
+     */
+    private void testViewCenter(MutableTransformer transformer, Point2D point) {
+        if(transformer instanceof LensTransformer) {
+            LensTransformer ht =
+                (LensTransformer)transformer;
+            double distanceFromCenter = ht.getDistanceFromCenter(point);
+            if(distanceFromCenter < 10) {
+                ht.setViewCenter(point);
+                dragOnLens = true;
+            } else if(Math.abs(distanceFromCenter - ht.getViewRadius()) < 10) {
+                edgeOffset = ht.getViewRadius() - distanceFromCenter;
+                ht.setViewRadius(distanceFromCenter+edgeOffset);
+                dragOnEdge = true;
+            }
+        }
+    }
+    
+    /**
+     * unset the 'down' point and change the cursoe back to the system
+     * default cursor
+     */
+    public void mouseReleased(MouseEvent e) {
+        super.mouseReleased(e);
+        dragOnLens = false;
+        dragOnEdge = false;
+        edgeOffset = 0;
+    }
+    
+    /**
+     * check the modifiers. If accepted, move or resize the lens according
+     * to the dragging of the mouse pointer
+     * @param e the event
+     */
+    public void mouseDragged(MouseEvent e) {
+        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+        MutableTransformer vt = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
+        if(vt instanceof LensTransformer) {
+        	vt = ((LensTransformer)vt).getDelegate();
+        }
+        Point2D p = vt.inverseTransform(e.getPoint());
+        boolean accepted = checkModifiers(e);
+
+        if(accepted ) {
+            MutableTransformer modelTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+            vv.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+            if(dragOnLens) {
+                setViewCenter(modelTransformer, p);
+                setViewCenter(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW), p);
+                e.consume();
+                vv.repaint();
+
+            } else if(dragOnEdge) {
+
+                setViewRadius(modelTransformer, p);
+                setViewRadius(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW), p);
+                e.consume();
+                vv.repaint();
+                
+            } else {
+            	
+            	MutableTransformer mt = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+                Point2D iq = vt.inverseTransform(down);
+                iq = mt.inverseTransform(iq);
+                Point2D ip = vt.inverseTransform(e.getPoint());
+                ip = mt.inverseTransform(ip);
+                float dx = (float) (ip.getX()-iq.getX());
+                float dy = (float) (ip.getY()-iq.getY());
+                
+                modelTransformer.translate(dx, dy);
+                down.x = e.getX();
+                down.y = e.getY();
+            }
+        }
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ModalGraphMouse.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ModalGraphMouse.java
new file mode 100644
index 0000000..cfa70d6
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ModalGraphMouse.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 26, 2005
+ */
+
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.ItemListener;
+
+import edu.uci.ics.jung.visualization.VisualizationViewer.GraphMouse;
+
+/**
+ * Interface for a GraphMouse that supports modality.
+ * 
+ * @author Tom Nelson 
+ *
+ */
+public interface ModalGraphMouse extends GraphMouse {
+    
+    void setMode(Mode mode);
+    
+    /**
+     * @return Returns the modeListener.
+     */
+    ItemListener getModeListener();
+    
+    /**
+     */
+    enum Mode { TRANSFORMING, PICKING, ANNOTATING, EDITING }
+    
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ModalLensGraphMouse.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ModalLensGraphMouse.java
new file mode 100644
index 0000000..e28b14e
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ModalLensGraphMouse.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 26, 2005
+ */
+
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+/**
+ * an implementation of the AbstractModalGraphMouse that includes plugins for
+ * manipulating a view that is using a LensTransformer.
+ * 
+ * @author Tom Nelson 
+ *
+ */
+public class ModalLensGraphMouse extends AbstractModalGraphMouse implements
+        ModalGraphMouse {
+
+	/**
+	 * not included in the base class
+	 */
+    protected LensMagnificationGraphMousePlugin magnificationPlugin;
+    
+    public ModalLensGraphMouse() {
+        this(1.1f, 1/1.1f);
+    }
+
+    public ModalLensGraphMouse(float in, float out) {
+        this(in, out, new LensMagnificationGraphMousePlugin());
+    }
+
+    public ModalLensGraphMouse(LensMagnificationGraphMousePlugin magnificationPlugin) {
+        this(1.1f, 1/1.1f, magnificationPlugin);
+    }
+    
+    public ModalLensGraphMouse(float in, float out, LensMagnificationGraphMousePlugin magnificationPlugin) {
+    	super(in,out);
+        this.in = in;
+        this.out = out;
+        this.magnificationPlugin = magnificationPlugin;
+        loadPlugins();
+        setModeKeyListener(new ModeKeyAdapter(this));
+    }
+    
+    protected void loadPlugins() {
+        pickingPlugin = new PickingGraphMousePlugin<Object, Object>();
+        animatedPickingPlugin = new AnimatedPickingGraphMousePlugin<Object, Object>();
+        translatingPlugin = new LensTranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK);
+        scalingPlugin = new ScalingGraphMousePlugin(new CrossoverScalingControl(), 0, in, out);
+        rotatingPlugin = new RotatingGraphMousePlugin();
+        shearingPlugin = new ShearingGraphMousePlugin();
+        
+        add(magnificationPlugin);
+        add(scalingPlugin);
+
+        setMode(Mode.TRANSFORMING);
+    }
+    public static class ModeKeyAdapter extends KeyAdapter {
+    	private char t = 't';
+    	private char p = 'p';
+    	protected ModalGraphMouse graphMouse;
+
+    	public ModeKeyAdapter(ModalGraphMouse graphMouse) {
+			this.graphMouse = graphMouse;
+		}
+
+		public ModeKeyAdapter(char t, char p, ModalGraphMouse graphMouse) {
+			this.t = t;
+			this.p = p;
+			this.graphMouse = graphMouse;
+		}
+		
+		public void keyTyped(KeyEvent event) {
+			char keyChar = event.getKeyChar();
+			if(keyChar == t) {
+				((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+				graphMouse.setMode(Mode.TRANSFORMING);
+			} else if(keyChar == p) {
+				((Component)event.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+				graphMouse.setMode(Mode.PICKING);
+			}
+		}
+    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ModalSatelliteGraphMouse.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ModalSatelliteGraphMouse.java
new file mode 100644
index 0000000..134dde8
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ModalSatelliteGraphMouse.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 26, 2005
+ */
+
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.InputEvent;
+/**
+ * 
+ * @author Tom Nelson 
+ *
+ */
+ at SuppressWarnings("rawtypes")
+public class ModalSatelliteGraphMouse extends DefaultModalGraphMouse implements
+        ModalGraphMouse {
+
+    public ModalSatelliteGraphMouse() {
+        this(1.1f, 1/1.1f);
+    }
+
+    public ModalSatelliteGraphMouse(float in, float out) {
+        super(in, out);
+    }
+    
+	protected void loadPlugins() {
+        pickingPlugin = new PickingGraphMousePlugin();
+        animatedPickingPlugin = new SatelliteAnimatedPickingGraphMousePlugin();
+        translatingPlugin = new SatelliteTranslatingGraphMousePlugin(InputEvent.BUTTON1_MASK);
+        scalingPlugin = new SatelliteScalingGraphMousePlugin(new CrossoverScalingControl(), 0);
+        rotatingPlugin = new SatelliteRotatingGraphMousePlugin();
+        shearingPlugin = new SatelliteShearingGraphMousePlugin();
+        
+        add(scalingPlugin);
+        
+        setMode(Mode.TRANSFORMING);
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/MouseListenerTranslator.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/MouseListenerTranslator.java
new file mode 100644
index 0000000..418845b
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/MouseListenerTranslator.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Feb 17, 2004
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+
+/**
+ * This class translates mouse clicks into vertex clicks
+ * 
+ * @author danyelf
+ */
+public class MouseListenerTranslator<V, E> extends MouseAdapter {
+
+	private VisualizationViewer<V,E> vv;
+	private GraphMouseListener<V> gel;
+
+	/**
+	 * @param gel listens for mouse events
+	 * @param vv the viewer used for visualization
+	 */
+	public MouseListenerTranslator(GraphMouseListener<V> gel, VisualizationViewer<V,E> vv) {
+		this.gel = gel;
+		this.vv = vv;
+	}
+	
+	/**
+	 * Transform the point to the coordinate system in the
+	 * VisualizationViewer, then use either PickSuuport
+	 * (if available) or Layout to find a Vertex
+	 * @param point
+	 * @return
+	 */
+	private V getVertex(Point2D point) {
+	    // adjust for scale and offset in the VisualizationViewer
+	    Point2D p = point;
+	    	//vv.getRenderContext().getBasicTransformer().inverseViewTransform(point);
+	    GraphElementAccessor<V,E> pickSupport = vv.getPickSupport();
+        Layout<V,E> layout = vv.getGraphLayout();
+	    V v = null;
+	    if(pickSupport != null) {
+	        v = pickSupport.getVertex(layout, p.getX(), p.getY());
+	    } 
+	    return v;
+	}
+	/**
+	 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
+	 */
+	public void mouseClicked(MouseEvent e) {
+	    V v = getVertex(e.getPoint());
+		if ( v != null ) {
+			gel.graphClicked(v, e );
+		}
+	}
+
+	/**
+	 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
+	 */
+	public void mousePressed(MouseEvent e) {
+		V v = getVertex(e.getPoint());
+		if ( v != null ) {
+			gel.graphPressed(v, e );
+		}
+	}
+
+	/**
+	 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
+	 */
+	public void mouseReleased(MouseEvent e) {
+		V v = getVertex(e.getPoint());
+		if ( v != null ) {
+			gel.graphReleased(v, e );
+		}
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/PickingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/PickingGraphMousePlugin.java
new file mode 100644
index 0000000..fb41112
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/PickingGraphMousePlugin.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Collection;
+
+import javax.swing.JComponent;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.VisualizationServer.Paintable;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+
+/** 
+ * PickingGraphMousePlugin supports the picking of graph elements
+ * with the mouse. MouseButtonOne picks a single vertex
+ * or edge, and MouseButtonTwo adds to the set of selected Vertices
+ * or EdgeType. If a Vertex is selected and the mouse is dragged while
+ * on the selected Vertex, then that Vertex will be repositioned to
+ * follow the mouse until the button is released.
+ * 
+ * @author Tom Nelson
+ */
+public class PickingGraphMousePlugin<V, E> extends AbstractGraphMousePlugin
+    implements MouseListener, MouseMotionListener {
+
+	/**
+	 * the picked Vertex, if any
+	 */
+    protected V vertex;
+    
+    /**
+     * the picked Edge, if any
+     */
+    protected E edge;
+    
+    /**
+     * the x distance from the picked vertex center to the mouse point
+     */
+    protected double offsetx;
+    
+    /**
+     * the y distance from the picked vertex center to the mouse point
+     */
+    protected double offsety;
+    
+    /**
+     * controls whether the Vertices may be moved with the mouse
+     */
+    protected boolean locked;
+    
+    /**
+     * additional modifiers for the action of adding to an existing
+     * selection
+     */
+    protected int addToSelectionModifiers;
+    
+    /**
+     * used to draw a rectangle to contain picked vertices
+     */
+    protected Rectangle2D rect = new Rectangle2D.Float();
+    
+    /**
+     * the Paintable for the lens picking rectangle
+     */
+    protected Paintable lensPaintable;
+    
+    /**
+     * color for the picking rectangle
+     */
+    protected Color lensColor = Color.cyan;
+    
+    /**
+	 * create an instance with default settings
+	 */
+	public PickingGraphMousePlugin() {
+	    this(InputEvent.BUTTON1_MASK, InputEvent.BUTTON1_MASK | InputEvent.SHIFT_MASK);
+	}
+
+	/**
+	 * create an instance with overides
+	 * @param selectionModifiers for primary selection
+	 * @param addToSelectionModifiers for additional selection
+	 */
+    public PickingGraphMousePlugin(int selectionModifiers, int addToSelectionModifiers) {
+        super(selectionModifiers);
+        this.addToSelectionModifiers = addToSelectionModifiers;
+        this.lensPaintable = new LensPaintable();
+        this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+    }
+    
+    /**
+     * @return Returns the lensColor.
+     */
+    public Color getLensColor() {
+        return lensColor;
+    }
+
+    /**
+     * @param lensColor The lensColor to set.
+     */
+    public void setLensColor(Color lensColor) {
+        this.lensColor = lensColor;
+    }
+
+    /**
+     * a Paintable to draw the rectangle used to pick multiple
+     * Vertices
+     * @author Tom Nelson
+     *
+     */
+    class LensPaintable implements Paintable {
+
+        public void paint(Graphics g) {
+            Color oldColor = g.getColor();
+            g.setColor(lensColor);
+            ((Graphics2D)g).draw(rect);
+            g.setColor(oldColor);
+        }
+
+        public boolean useTransform() {
+            return false;
+        }
+    }
+
+	/**
+	 * For primary modifiers (default, MouseButton1):
+	 * pick a single Vertex or Edge that
+     * is under the mouse pointer. If no Vertex or edge is under
+     * the pointer, unselect all picked Vertices and edges, and
+     * set up to draw a rectangle for multiple selection
+     * of contained Vertices.
+     * For additional selection (default Shift+MouseButton1):
+     * Add to the selection, a single Vertex or Edge that is
+     * under the mouse pointer. If a previously picked Vertex
+     * or Edge is under the pointer, it is un-picked.
+     * If no vertex or Edge is under the pointer, set up
+     * to draw a multiple selection rectangle (as above)
+     * but do not unpick previously picked elements.
+	 * 
+	 * @param e the event
+	 */
+    @SuppressWarnings("unchecked")
+    public void mousePressed(MouseEvent e) {
+        down = e.getPoint();
+        VisualizationViewer<V,E> vv = (VisualizationViewer<V, E>)e.getSource();
+        GraphElementAccessor<V,E> pickSupport = vv.getPickSupport();
+        PickedState<V> pickedVertexState = vv.getPickedVertexState();
+        PickedState<E> pickedEdgeState = vv.getPickedEdgeState();
+        if(pickSupport != null && pickedVertexState != null) {
+            Layout<V,E> layout = vv.getGraphLayout();
+            if(e.getModifiers() == modifiers) {
+                rect.setFrameFromDiagonal(down,down);
+                // p is the screen point for the mouse event
+                Point2D ip = e.getPoint();
+
+                vertex = pickSupport.getVertex(layout, ip.getX(), ip.getY());
+                if(vertex != null) {
+                    if(pickedVertexState.isPicked(vertex) == false) {
+                    	pickedVertexState.clear();
+                    	pickedVertexState.pick(vertex, true);
+                    }
+                    // layout.getLocation applies the layout Function so
+                    // q is transformed by the layout Function only
+                    Point2D q = layout.apply(vertex);
+                    // transform the mouse point to graph coordinate system
+                    Point2D gp = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.LAYOUT, ip);
+
+                    offsetx = (float) (gp.getX()-q.getX());
+                    offsety = (float) (gp.getY()-q.getY());
+                } else if((edge = pickSupport.getEdge(layout, ip.getX(), ip.getY())) != null) {
+                    pickedEdgeState.clear();
+                    pickedEdgeState.pick(edge, true);
+                } else {
+                    vv.addPostRenderPaintable(lensPaintable);
+                	pickedEdgeState.clear();
+                    pickedVertexState.clear();
+                }
+                
+            } else if(e.getModifiers() == addToSelectionModifiers) {
+                vv.addPostRenderPaintable(lensPaintable);
+                rect.setFrameFromDiagonal(down,down);
+                Point2D ip = e.getPoint();
+                vertex = pickSupport.getVertex(layout, ip.getX(), ip.getY());
+                if(vertex != null) {
+                    boolean wasThere = pickedVertexState.pick(vertex, !pickedVertexState.isPicked(vertex));
+                    if(wasThere) {
+                        vertex = null;
+                    } else {
+
+                        // layout.getLocation applies the layout Function so
+                        // q is transformed by the layout Function only
+                        Point2D q = layout.apply(vertex);
+                        // translate mouse point to graph coord system
+                        Point2D gp = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.LAYOUT, ip);
+
+                        offsetx = (float) (gp.getX()-q.getX());
+                        offsety = (float) (gp.getY()-q.getY());
+                    }
+                } else if((edge = pickSupport.getEdge(layout, ip.getX(), ip.getY())) != null) {
+                    pickedEdgeState.pick(edge, !pickedEdgeState.isPicked(edge));
+                }
+            }
+        }
+        if(vertex != null) e.consume();
+    }
+
+    /**
+	 * If the mouse is dragging a rectangle, pick the
+	 * Vertices contained in that rectangle
+	 * 
+	 * clean up settings from mousePressed
+	 */
+    @SuppressWarnings("unchecked")
+    public void mouseReleased(MouseEvent e) {
+        VisualizationViewer<V,E> vv = (VisualizationViewer<V, E>)e.getSource();
+        if(e.getModifiers() == modifiers) {
+            if(down != null) {
+                Point2D out = e.getPoint();
+
+                if(vertex == null && heyThatsTooClose(down, out, 5) == false) {
+                    pickContainedVertices(vv, down, out, true);
+                }
+            }
+        } else if(e.getModifiers() == this.addToSelectionModifiers) {
+            if(down != null) {
+                Point2D out = e.getPoint();
+
+                if(vertex == null && heyThatsTooClose(down,out,5) == false) {
+                    pickContainedVertices(vv, down, out, false);
+                }
+            }
+        }
+        down = null;
+        vertex = null;
+        edge = null;
+        rect.setFrame(0,0,0,0);
+        vv.removePostRenderPaintable(lensPaintable);
+        vv.repaint();
+    }
+    
+    /**
+	 * If the mouse is over a picked vertex, drag all picked
+	 * vertices with the mouse.
+	 * If the mouse is not over a Vertex, draw the rectangle
+	 * to select multiple Vertices
+	 * 
+	 */
+    @SuppressWarnings("unchecked")
+    public void mouseDragged(MouseEvent e) {
+        if(locked == false) {
+            VisualizationViewer<V,E> vv = (VisualizationViewer<V, E>)e.getSource();
+            if(vertex != null) {
+                Point p = e.getPoint();
+                Point2D graphPoint = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(p);
+                Point2D graphDown = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(down);
+                Layout<V,E> layout = vv.getGraphLayout();
+                double dx = graphPoint.getX()-graphDown.getX();
+                double dy = graphPoint.getY()-graphDown.getY();
+                PickedState<V> ps = vv.getPickedVertexState();
+                
+                for(V v : ps.getPicked()) {
+                    Point2D vp = layout.apply(v);
+                    vp.setLocation(vp.getX()+dx, vp.getY()+dy);
+                    layout.setLocation(v, vp);
+                }
+                down = p;
+
+            } else {
+                Point2D out = e.getPoint();
+                if(e.getModifiers() == this.addToSelectionModifiers ||
+                        e.getModifiers() == modifiers) {
+                    rect.setFrameFromDiagonal(down,out);
+                }
+            }
+            if(vertex != null) e.consume();
+            vv.repaint();
+        }
+    }
+    
+    /**
+     * rejects picking if the rectangle is too small, like
+     * if the user meant to select one vertex but moved the
+     * mouse slightly
+     * @param p
+     * @param q
+     * @param min
+     * @return
+     */
+    private boolean heyThatsTooClose(Point2D p, Point2D q, double min) {
+        return Math.abs(p.getX()-q.getX()) < min &&
+                Math.abs(p.getY()-q.getY()) < min;
+    }
+    
+    /**
+     * pick the vertices inside the rectangle created from points 'down' and 'out' (two diagonally
+     * opposed corners of the rectangle)
+     * 
+     * @param vv the viewer containing the layout and picked state
+     * @param down one corner of the rectangle
+     * @param out the other corner of the rectangle
+     * @param clear whether to reset existing picked state
+     */
+    protected void pickContainedVertices(VisualizationViewer<V,E> vv, Point2D down, Point2D out, boolean clear) {
+        
+        Layout<V,E> layout = vv.getGraphLayout();
+        PickedState<V> pickedVertexState = vv.getPickedVertexState();
+        
+        Rectangle2D pickRectangle = new Rectangle2D.Double();
+        pickRectangle.setFrameFromDiagonal(down,out);
+         
+        if(pickedVertexState != null) {
+            if(clear) {
+            	pickedVertexState.clear();
+            }
+            GraphElementAccessor<V,E> pickSupport = vv.getPickSupport();
+
+            Collection<V> picked = pickSupport.getVertices(layout, pickRectangle);
+            for(V v : picked) {
+            	pickedVertexState.pick(v, true);
+            }
+        }
+    }
+
+    public void mouseClicked(MouseEvent e) {
+    }
+
+    public void mouseEntered(MouseEvent e) {
+        JComponent c = (JComponent)e.getSource();
+        c.setCursor(cursor);
+    }
+
+    public void mouseExited(MouseEvent e) {
+        JComponent c = (JComponent)e.getSource();
+        c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    }
+
+    public void mouseMoved(MouseEvent e) {
+    }
+
+    /**
+     * @return Returns the locked.
+     */
+    public boolean isLocked() {
+        return locked;
+    }
+
+    /**
+     * @param locked The locked to set.
+     */
+    public void setLocked(boolean locked) {
+        this.locked = locked;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/PluggableGraphMouse.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/PluggableGraphMouse.java
new file mode 100644
index 0000000..879427d
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/PluggableGraphMouse.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 7, 2005
+ */
+
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+
+/**
+ * a GraphMouse that accepts plugins for various mouse events.
+ * 
+ * @author Tom Nelson 
+ *
+ *
+ */
+public class PluggableGraphMouse implements VisualizationViewer.GraphMouse {
+
+    MouseListener[] mouseListeners;
+    MouseMotionListener[] mouseMotionListeners;
+    MouseWheelListener[] mouseWheelListeners;
+    Set<GraphMousePlugin> mousePluginList = new LinkedHashSet<GraphMousePlugin>();
+    Set<MouseMotionListener> mouseMotionPluginList = new LinkedHashSet<MouseMotionListener>();
+    Set<MouseWheelListener> mouseWheelPluginList = new LinkedHashSet<MouseWheelListener>();
+
+    public void add(GraphMousePlugin plugin) {
+        if(plugin instanceof MouseListener) {
+            mousePluginList.add(plugin);
+            mouseListeners = null;
+        }
+        if(plugin instanceof MouseMotionListener) {
+            mouseMotionPluginList.add((MouseMotionListener)plugin);
+            mouseMotionListeners = null;
+        }
+        if(plugin instanceof MouseWheelListener) {
+            mouseWheelPluginList.add((MouseWheelListener)plugin);
+            mouseWheelListeners = null;
+        }
+    }
+
+    public void remove(GraphMousePlugin plugin) {
+        if(plugin instanceof MouseListener) {
+            boolean wasThere = mousePluginList.remove(plugin);
+            if(wasThere) mouseListeners = null;
+        }
+        if(plugin instanceof MouseMotionListener) {
+            boolean wasThere = mouseMotionPluginList.remove(plugin);
+            if(wasThere) mouseMotionListeners = null;
+        }
+        if(plugin instanceof MouseWheelListener) {
+            boolean wasThere = mouseWheelPluginList.remove(plugin);
+            if(wasThere) mouseWheelListeners = null;
+        }
+    }
+    
+    private void checkMouseListeners() {
+        if(mouseListeners == null) {
+            mouseListeners = (MouseListener[])
+            mousePluginList.toArray(new MouseListener[mousePluginList.size()]);
+        }
+    }
+    
+    private void checkMouseMotionListeners() {
+        if(mouseMotionListeners == null){
+            mouseMotionListeners = (MouseMotionListener[])
+            mouseMotionPluginList.toArray(new MouseMotionListener[mouseMotionPluginList.size()]);
+        } 
+    }
+    
+    private void checkMouseWheelListeners() {
+        if(mouseWheelListeners == null) {
+            mouseWheelListeners = (MouseWheelListener[])
+            mouseWheelPluginList.toArray(new MouseWheelListener[mouseWheelPluginList.size()]);
+        }
+    }
+
+    public void mouseClicked(MouseEvent e) {
+        checkMouseListeners();
+        for(int i=0; i<mouseListeners.length; i++) {
+            mouseListeners[i].mouseClicked(e);
+            if(e.isConsumed()) break;
+        }
+    }
+    
+    public void mousePressed(MouseEvent e) {
+        checkMouseListeners();
+        for(int i=0; i<mouseListeners.length; i++) {
+            mouseListeners[i].mousePressed(e);
+            if(e.isConsumed()) break;
+        }
+    }
+    
+    public void mouseReleased(MouseEvent e) {
+        checkMouseListeners();
+        for(int i=0; i<mouseListeners.length; i++) {
+            mouseListeners[i].mouseReleased(e);
+            if(e.isConsumed()) break;
+        }
+    }
+    
+    public void mouseEntered(MouseEvent e) {
+        checkMouseListeners();
+        for(int i=0; i<mouseListeners.length; i++) {
+            mouseListeners[i].mouseEntered(e);
+            if(e.isConsumed()) break;
+        }
+    }
+    
+    public void mouseExited(MouseEvent e) {
+        checkMouseListeners();
+        for(int i=0; i<mouseListeners.length; i++) {
+            mouseListeners[i].mouseExited(e);
+            if(e.isConsumed()) break;
+        }
+    }
+    
+    public void mouseDragged(MouseEvent e) {
+        checkMouseMotionListeners();
+        for(int i=0; i<mouseMotionListeners.length; i++) {
+            mouseMotionListeners[i].mouseDragged(e);
+            if(e.isConsumed()) break;
+        }
+    }
+    
+    public void mouseMoved(MouseEvent e) {
+        checkMouseMotionListeners();
+        for(int i=0; i<mouseMotionListeners.length; i++) {
+            mouseMotionListeners[i].mouseMoved(e);
+            if(e.isConsumed()) break;
+        }
+    }
+    
+    public void mouseWheelMoved(MouseWheelEvent e) {
+        checkMouseWheelListeners();
+        for(int i=0; i<mouseWheelListeners.length; i++) {
+            mouseWheelListeners[i].mouseWheelMoved(e);
+            if(e.isConsumed()) break;
+        }
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/RotatingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/RotatingGraphMousePlugin.java
new file mode 100644
index 0000000..edb6fc7
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/RotatingGraphMousePlugin.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.RenderingHints;
+import java.awt.Toolkit;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+import java.awt.image.BufferedImage;
+import java.util.Collections;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/** 
+ * RotatingGraphMouse provides the abiity to rotate the graph using
+ * the mouse. By default, it is activated by mouse button one drag
+ * with the shift key pressed. The modifiers can be overridden so that
+ * a different mouse/key combination activates the rotation
+ * 
+ * @author Tom Nelson
+ */
+public class RotatingGraphMousePlugin extends AbstractGraphMousePlugin
+    implements MouseListener, MouseMotionListener {
+
+	/**
+	 * create an instance with default modifier values
+	 */
+	public RotatingGraphMousePlugin() {
+	    this(MouseEvent.BUTTON1_MASK | MouseEvent.SHIFT_MASK);
+	}
+
+	/**
+	 * create an instance with passed zoom in/out values
+	 * @param modifiers the event modifiers to trigger rotation
+	 */
+	public RotatingGraphMousePlugin(int modifiers) {
+	    super(modifiers);
+	    Dimension cd = Toolkit.getDefaultToolkit().getBestCursorSize(16,16);
+        BufferedImage cursorImage = 
+        		new BufferedImage(cd.width,cd.height,BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g = cursorImage.createGraphics();
+        g.addRenderingHints(Collections.singletonMap(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
+        g.setColor(new Color(0,0,0,0));
+        g.fillRect(0,0,16,16);
+
+        int left = 0;
+        int top = 0;
+        int right = 15;
+        int bottom = 15;
+        
+        g.setColor(Color.white);
+        g.setStroke(new BasicStroke(3));
+        // top bent line
+        g.drawLine(left+2,top+6,right/2+1,top);
+        g.drawLine(right/2+1,top,right-2,top+5);
+        // bottom bent line
+        g.drawLine(left+2,bottom-6,right/2,bottom);
+        g.drawLine(right/2,bottom,right-2,bottom-6);
+        // top arrow
+        g.drawLine(left+2,top+6,left+5,top+6);
+        g.drawLine(left+2,top+6,left+2,top+3);
+        // bottom arrow
+        g.drawLine(right-2,bottom-6,right-6,bottom-6);
+        g.drawLine(right-2, bottom-6,right-2,bottom-3);
+
+        
+        g.setColor(Color.black);
+        g.setStroke(new BasicStroke(1));
+        // top bent line
+        g.drawLine(left+2,top+6,right/2+1,top);
+        g.drawLine(right/2+1,top,right-2,top+5);
+        // bottom bent line
+        g.drawLine(left+2,bottom-6,right/2,bottom);
+        g.drawLine(right/2,bottom,right-2,bottom-6);
+        // top arrow
+        g.drawLine(left+2,top+6,left+5,top+6);
+        g.drawLine(left+2,top+6,left+2,top+3);
+        // bottom arrow
+        g.drawLine(right-2,bottom-6,right-6,bottom-6);
+        g.drawLine(right-2, bottom-6,right-2,bottom-3);
+
+        g.dispose();
+        
+        cursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(), "RotateCursor");
+	}
+
+    /**
+     * save the 'down' point and check the modifiers. If the
+     * modifiers are accepted, set the cursor to the 'hand' cursor
+	 * @param e the event
+	 */
+    public void mousePressed(MouseEvent e) {
+        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+           boolean accepted = checkModifiers(e);
+           down = e.getPoint();
+          if(accepted) {
+               vv.setCursor(cursor);
+           }
+    }
+    
+	/**
+     * unset the down point and change the cursor back to the default
+	 */
+    public void mouseReleased(MouseEvent e) {
+        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+        down = null;
+        vv.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    }
+    
+    /**
+     * check the modifiers. If accepted, use the mouse drag motion
+     * to rotate the graph
+	 */
+    public void mouseDragged(MouseEvent e) {
+        if(down == null) return;
+        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+        boolean accepted = checkModifiers(e);
+        if(accepted) {
+            MutableTransformer modelTransformer =
+                vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+            // rotate
+            vv.setCursor(cursor);
+            
+            Point2D center = vv.getCenter();
+            Point2D q = down;
+            Point2D p = e.getPoint();
+            Point2D v1 = new Point2D.Double(center.getX()-p.getX(), center.getY()-p.getY());
+            Point2D v2 = new Point2D.Double(center.getX()-q.getX(), center.getY()-q.getY());
+            double theta = angleBetween(v1, v2);
+            modelTransformer.rotate(theta, vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, center));
+            down.x = e.getX();
+            down.y = e.getY();
+        
+            e.consume();
+        }
+    }
+    
+    /**
+     * Returns the angle between two vectors from the origin
+     * to points v1 and v2.
+     * @param v1 the first point
+     * @param v2 the second point
+     * @return the angle between two vectors from the origin through points v1 and v2
+     */
+    protected double angleBetween(Point2D v1, Point2D v2) {
+        double x1 = v1.getX();
+        double y1 = v1.getY();
+        double x2 = v2.getX();
+        double y2 = v2.getY();
+        // cross product for direction
+        double cross = x1*y2 - x2*y1;
+        int cw = 1;
+        if(cross > 0) {
+            cw = -1;
+        } 
+        // dot product for angle
+        double angle = 
+            cw*Math.acos( ( x1*x2 + y1*y2 ) / 
+                ( Math.sqrt( x1*x1 + y1*y1 ) * 
+                        Math.sqrt( x2*x2 + y2*y2 ) ) );
+        if(Double.isNaN(angle)) {
+            angle = 0;
+        }
+        return angle;
+    }
+
+    public void mouseClicked(MouseEvent e) {
+    }
+
+    public void mouseEntered(MouseEvent e) {
+    }
+
+    public void mouseExited(MouseEvent e) {
+    }
+
+    public void mouseMoved(MouseEvent e) {
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteAnimatedPickingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteAnimatedPickingGraphMousePlugin.java
new file mode 100644
index 0000000..17a8cae
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteAnimatedPickingGraphMousePlugin.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+
+/** 
+ * A version of the AnimatedPickingGraphMousePlugin that is for
+ * the SatelliteVisualizationViewer. The difference it that when
+ * you pick a Vertex in the Satellite View, the 'master view' is
+ * translated to move that Vertex to the center.
+ * @see AnimatedPickingGraphMousePlugin
+ * @author Tom Nelson
+ */
+public class SatelliteAnimatedPickingGraphMousePlugin<V,E> extends AnimatedPickingGraphMousePlugin<V,E>
+    implements MouseListener, MouseMotionListener {
+
+    /**
+	 * create an instance 
+	 * 
+	 */
+	public SatelliteAnimatedPickingGraphMousePlugin() {
+	    this(InputEvent.BUTTON1_MASK  | InputEvent.CTRL_MASK);
+	}
+
+    public SatelliteAnimatedPickingGraphMousePlugin(int selectionModifiers) {
+        super(selectionModifiers);
+    }
+
+    /**
+     * override subclass method to translate the master view instead
+     * of this satellite view
+     * 
+     */
+    @SuppressWarnings("unchecked")
+    public void mouseReleased(MouseEvent e) {
+    		if (e.getModifiers() == modifiers) {
+			final VisualizationViewer<V,E> vv = (VisualizationViewer<V, E>) e.getSource();
+			if (vv instanceof SatelliteVisualizationViewer) {
+				final VisualizationViewer<V,E> vvMaster = 
+					((SatelliteVisualizationViewer<V, E>) vv).getMaster();
+
+				if (vertex != null) {
+					Layout<V,E> layout = vvMaster.getGraphLayout();
+					Point2D q = layout.apply(vertex);
+					Point2D lvc = 
+						vvMaster.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.LAYOUT, vvMaster.getCenter());
+					final double dx = (lvc.getX() - q.getX()) / 10;
+					final double dy = (lvc.getY() - q.getY()) / 10;
+
+					Runnable animator = new Runnable() {
+
+						public void run() {
+							for (int i = 0; i < 10; i++) {
+								vvMaster.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).translate(dx,
+										dy);
+								try {
+									Thread.sleep(100);
+								} catch (InterruptedException ex) {
+								}
+							}
+						}
+					};
+					Thread thread = new Thread(animator);
+					thread.start();
+				}
+			}
+		}
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteRotatingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteRotatingGraphMousePlugin.java
new file mode 100644
index 0000000..20434bb
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteRotatingGraphMousePlugin.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 15, 2005
+ */
+
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.MouseEvent;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/**
+ * Mouse events in the SatelliteView that match the modifiers
+ * will cause the Main view to rotate
+ * @see RotatingGraphMousePlugin
+ * @author Tom Nelson 
+ *
+ */
+public class SatelliteRotatingGraphMousePlugin extends RotatingGraphMousePlugin {
+
+    public SatelliteRotatingGraphMousePlugin() {
+        super();
+    }
+
+    public SatelliteRotatingGraphMousePlugin(int modifiers) {
+        super(modifiers);
+    }
+    /**
+     * check the modifiers. If accepted, use the mouse drag motion
+     * to rotate the graph in the master view
+     */
+    public void mouseDragged(MouseEvent e) {
+        if(down == null) return;
+        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+        boolean accepted = checkModifiers(e);
+        if(accepted) {
+            if(vv instanceof SatelliteVisualizationViewer) {
+                VisualizationViewer<?, ?> vvMaster = 
+                    ((SatelliteVisualizationViewer<?, ?>)vv).getMaster();
+                
+                MutableTransformer modelTransformerMaster = 
+                	vvMaster.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+
+                // rotate
+                vv.setCursor(cursor);
+                // I want to compute rotation based on the view coordinates of the
+                // lens center in the satellite view.
+                // translate the master view center to layout coords, then translate
+                // that point to the satellite view's view coordinate system....
+                Point2D center = vv.getRenderContext().getMultiLayerTransformer().transform(vvMaster.getRenderContext().getMultiLayerTransformer().inverseTransform(vvMaster.getCenter()));
+                Point2D q = down;
+                Point2D p = e.getPoint();
+                Point2D v1 = new Point2D.Double(center.getX()-p.getX(), center.getY()-p.getY());
+                Point2D v2 = new Point2D.Double(center.getX()-q.getX(), center.getY()-q.getY());
+                double theta = angleBetween(v1, v2);
+                modelTransformerMaster.rotate(-theta, 
+                        vvMaster.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, vvMaster.getCenter()));
+                down.x = e.getX();
+                down.y = e.getY();
+            } 
+            e.consume();
+        }
+    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteScalingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteScalingGraphMousePlugin.java
new file mode 100644
index 0000000..2972aae
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteScalingGraphMousePlugin.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 15, 2005
+ */
+
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.MouseWheelEvent;
+
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+
+/**
+ * Overrides ScalingGraphMousePlugin so that mouse events in the
+ * satellite view will cause scaling in the main view
+ * 
+ * @see ScalingGraphMousePlugin
+ * @author Tom Nelson 
+ *
+ */
+public class SatelliteScalingGraphMousePlugin extends ScalingGraphMousePlugin {
+
+    public SatelliteScalingGraphMousePlugin(ScalingControl scaler, int modifiers) {
+        super(scaler, modifiers);
+    }
+
+    public SatelliteScalingGraphMousePlugin(ScalingControl scaler, int modifiers, float in, float out) {
+        super(scaler, modifiers, in, out);
+    }
+    
+    /**
+     * zoom the master view display in or out, depending on the direction of the
+     * mouse wheel motion.
+     */
+    public void mouseWheelMoved(MouseWheelEvent e) {
+        boolean accepted = checkModifiers(e);
+        if(accepted == true) {
+            VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+
+            if(vv instanceof SatelliteVisualizationViewer) {
+                VisualizationViewer<?, ?> vvMaster = 
+                    ((SatelliteVisualizationViewer<?, ?>)vv).getMaster();
+
+                int amount = e.getWheelRotation();
+                
+                if(amount > 0) {
+                    scaler.scale(vvMaster, in, vvMaster.getCenter());
+
+                } else if(amount < 0) {
+                    scaler.scale(vvMaster, out, vvMaster.getCenter());
+                }
+                e.consume();
+                vv.repaint();
+            }
+        }
+    }
+
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteShearingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteShearingGraphMousePlugin.java
new file mode 100644
index 0000000..a1dd66e
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteShearingGraphMousePlugin.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 15, 2005
+ */
+
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Dimension;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/**
+ * Overrides ShearingGraphMousePlugin so that mouse events in the
+ * satellite view cause shearing of the main view
+ * 
+ * @see ShearingGraphMousePlugin
+ * @author Tom Nelson 
+ *
+ */
+public class SatelliteShearingGraphMousePlugin extends ShearingGraphMousePlugin {
+
+    public SatelliteShearingGraphMousePlugin() {
+        super();
+    }
+
+    public SatelliteShearingGraphMousePlugin(int modifiers) {
+        super(modifiers);
+    }
+    
+    /**
+     * overridden to shear the main view
+     */
+    public void mouseDragged(MouseEvent e) {
+        if(down == null) return;
+        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+        boolean accepted = checkModifiers(e);
+        if(accepted) {
+            if(vv instanceof SatelliteVisualizationViewer) {
+                VisualizationViewer<?, ?> vvMaster = 
+                    ((SatelliteVisualizationViewer<?, ?>)vv).getMaster();
+                
+                MutableTransformer modelTransformerMaster = 
+                	vvMaster.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+
+                vv.setCursor(cursor);
+                Point2D q = down;
+                Point2D p = e.getPoint();
+                float dx = (float) (p.getX()-q.getX());
+                float dy = (float) (p.getY()-q.getY());
+
+                Dimension d = vv.getSize();
+                float shx = 2.f*dx/d.height;
+                float shy = 2.f*dy/d.width;
+                // I want to compute shear based on the view coordinates of the
+                // lens center in the satellite view.
+                // translate the master view center to layout coords, then translate
+                // that point to the satellite view's view coordinate system....
+                Point2D center = vv.getRenderContext().getMultiLayerTransformer().transform(vvMaster.getRenderContext().getMultiLayerTransformer().inverseTransform(vvMaster.getCenter()));
+                if(p.getX() < center.getX()) {
+                    shy = -shy;
+                }
+                if(p.getY() < center.getY()) {
+                    shx = -shx;
+                }
+                modelTransformerMaster.shear(-shx, -shy, vvMaster.getCenter());
+
+                down.x = e.getX();
+                down.y = e.getY();
+            }
+            e.consume();
+        }
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteTranslatingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteTranslatingGraphMousePlugin.java
new file mode 100644
index 0000000..70beeeb
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteTranslatingGraphMousePlugin.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 15, 2005
+ */
+
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Cursor;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/**
+ * Overrides TranslatingGraphMousePlugin so that mouse events in
+ * the satellite view cause translating of the main view
+ * 
+ * @see TranslatingGraphMousePlugin
+ * @author Tom Nelson 
+ *
+ */
+public class SatelliteTranslatingGraphMousePlugin extends
+        TranslatingGraphMousePlugin {
+
+    public SatelliteTranslatingGraphMousePlugin() {
+        super();
+    }
+
+    public SatelliteTranslatingGraphMousePlugin(int modifiers) {
+        super(modifiers);
+    }
+    
+    /**
+     * Check the modifiers. If accepted, translate the main view according
+     * to the dragging of the mouse pointer in the satellite view
+     * @param e the event
+     */
+    public void mouseDragged(MouseEvent e) {
+        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+        boolean accepted = checkModifiers(e);
+        if(accepted) {
+            if(vv instanceof SatelliteVisualizationViewer) {
+                VisualizationViewer<?, ?> vvMaster = 
+                    ((SatelliteVisualizationViewer<?, ?>)vv).getMaster();
+                
+                MutableTransformer modelTransformerMaster = 
+                	vvMaster.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+                vv.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+                try {
+                    Point2D q = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(down);
+                    Point2D p = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(e.getPoint());
+                    float dx = (float) (p.getX()-q.getX());
+                    float dy = (float) (p.getY()-q.getY());
+                    
+                    modelTransformerMaster.translate(-dx, -dy);
+                    down.x = e.getX();
+                    down.y = e.getY();
+                } catch(RuntimeException ex) {
+                    System.err.println("down = "+down+", e = "+e);
+                    throw ex;
+                }
+            }
+            e.consume();
+        }
+    }
+
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteVisualizationViewer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteVisualizationViewer.java
new file mode 100644
index 0000000..2b01838
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SatelliteVisualizationViewer.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 15, 2005
+ */
+
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.transform.MutableAffineTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer;
+
+/**
+ * A VisualizationViewer that can act as a satellite view for another
+ * (master) VisualizationViewer. In this view, the full graph is always visible
+ * and all mouse actions affect the graph in the master view.
+ * 
+ * A rectangular shape in the satellite view shows the visible bounds of
+ * the master view. 
+ * 
+ * @author Tom Nelson 
+ *
+ * 
+ */
+ at SuppressWarnings("serial")
+public class SatelliteVisualizationViewer<V, E> 
+	extends VisualizationViewer<V,E> {
+    
+    /**
+     * the master VisualizationViewer that this is a satellite view for
+     */
+    protected VisualizationViewer<V,E> master;
+    
+    /**
+     * @param master the master VisualizationViewer for which this is a satellite view
+     * @param preferredSize the specified size of the component
+     */
+    public SatelliteVisualizationViewer(VisualizationViewer<V,E> master,
+    		Dimension preferredSize) {
+        super(master.getModel(), preferredSize);
+        this.master = master;
+        
+        // create a graph mouse with custom plugins to affect the master view
+        ModalGraphMouse gm = new ModalSatelliteGraphMouse();
+        setGraphMouse(gm);
+        
+        // this adds the Lens to the satellite view
+        addPreRenderPaintable(new ViewLens<V,E>(this, master));
+        
+        // get a copy of the current layout transform
+        // it may have been scaled to fit the graph
+        AffineTransform modelLayoutTransform =
+            new AffineTransform(master.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getTransform());
+        
+        // I want no layout transformations in the satellite view
+        // this resets the auto-scaling that occurs in the super constructor
+        getRenderContext().getMultiLayerTransformer().setTransformer(Layer.LAYOUT, new MutableAffineTransformer(modelLayoutTransform));
+        
+        // make sure the satellite listens for changes in the master
+        master.addChangeListener(this);
+        
+        // share the picked state of the master
+        setPickedVertexState(master.getPickedVertexState());
+        setPickedEdgeState(master.getPickedEdgeState());
+    }
+
+    /**
+     * @return Returns the master.
+     */
+    public VisualizationViewer<V,E> getMaster() {
+        return master;
+    }
+    
+    /**
+     * A four-sided shape that represents the visible part of the
+     * master view and is drawn in the satellite view
+     * 
+     * @author Tom Nelson 
+     *
+     *
+     */
+    static class ViewLens<V,E> implements Paintable {
+
+        VisualizationViewer<V,E> master;
+        VisualizationViewer<V,E> vv;
+        
+        public ViewLens(VisualizationViewer<V,E> vv, VisualizationViewer<V,E> master) {
+            this.vv = vv;
+            this.master = master;
+        }
+        public void paint(Graphics g) {
+            ShapeTransformer masterViewTransformer = 
+            	master.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
+            ShapeTransformer masterLayoutTransformer = master.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+            ShapeTransformer vvLayoutTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+
+            Shape lens = master.getBounds();
+            lens = masterViewTransformer.inverseTransform(lens);
+            lens = masterLayoutTransformer.inverseTransform(lens);
+            lens = vvLayoutTransformer.transform(lens);
+            Graphics2D g2d = (Graphics2D)g;
+            Color old = g.getColor();
+            Color lensColor = master.getBackground();
+            vv.setBackground(lensColor.darker());
+            g.setColor(lensColor);
+            g2d.fill(lens);
+            g.setColor(Color.gray);
+            g2d.draw(lens);
+            g.setColor(old);
+        }
+
+        public boolean useTransform() {
+            return true;
+        }
+    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ScalingControl.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ScalingControl.java
new file mode 100644
index 0000000..ca6e223
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ScalingControl.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.VisualizationServer;
+
+public interface ScalingControl {
+
+    /**
+     * zoom the display in or out
+     * @param vv the VisualizationViewer
+     * @param amount how much to adjust scale by
+     * @param at where to adjust scale from
+     */
+    void scale(VisualizationServer<?,?> vv, float amount, Point2D at);
+
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ScalingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ScalingGraphMousePlugin.java
new file mode 100644
index 0000000..6b25697
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ScalingGraphMousePlugin.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+
+/** 
+ * ScalingGraphMouse applies a scaling transformation to the graph layout.
+ * The Vertices get closer or farther apart, but do not themselves change
+ * size. ScalingGraphMouse uses MouseWheelEvents to apply the scaling.
+ * 
+ * @author Tom Nelson
+ */
+public class ScalingGraphMousePlugin extends AbstractGraphMousePlugin
+    implements MouseWheelListener {
+
+    /**
+     * the amount to zoom in by
+     */
+	protected float in = 1.1f;
+	/**
+	 * the amount to zoom out by
+	 */
+	protected float out = 1/1.1f;
+	
+	/**
+	 * whether to center the zoom at the current mouse position
+	 */
+	protected boolean zoomAtMouse = true;
+    
+    /**
+     * controls scaling operations
+     */
+    protected ScalingControl scaler;
+	
+    public ScalingGraphMousePlugin(ScalingControl scaler, int modifiers) {
+        this(scaler, modifiers, 1.1f, 1/1.1f);
+    }
+    
+    public ScalingGraphMousePlugin(ScalingControl scaler, int modifiers, float in, float out) {
+        super(modifiers);
+        this.scaler = scaler;
+        this.in = in;
+        this.out = out;
+    }
+   /**
+     * @param zoomAtMouse The zoomAtMouse to set.
+     */
+    public void setZoomAtMouse(boolean zoomAtMouse) {
+        this.zoomAtMouse = zoomAtMouse;
+    }
+    
+    public boolean checkModifiers(MouseEvent e) {
+        return e.getModifiers() == modifiers || (e.getModifiers() & modifiers) != 0;
+    }
+
+    /**
+	 * zoom the display in or out, depending on the direction of the
+	 * mouse wheel motion.
+	 */
+	public void mouseWheelMoved(MouseWheelEvent e) {
+        boolean accepted = checkModifiers(e);
+        if(accepted == true) {
+            VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+            Point2D mouse = e.getPoint();
+            Point2D center = vv.getCenter();
+            int amount = e.getWheelRotation();
+            if(zoomAtMouse) {
+                if(amount > 0) {
+                    scaler.scale(vv, in, mouse);
+                } else if(amount < 0) {
+                    scaler.scale(vv, out, mouse);
+                }
+            } else {
+                if(amount > 0) {
+                    scaler.scale(vv, in, center);
+                } else if(amount < 0) {
+                    scaler.scale(vv, out, center);
+                }
+            }
+            e.consume();
+            vv.repaint();
+        }
+	}
+    /**
+     * @return Returns the zoom in value.
+     */
+    public float getIn() {
+        return in;
+    }
+    /**
+     * @param in The zoom in value to set.
+     */
+    public void setIn(float in) {
+        this.in = in;
+    }
+    /**
+     * @return Returns the zoom out value.
+     */
+    public float getOut() {
+        return out;
+    }
+    /**
+     * @param out The zoom out value to set.
+     */
+    public void setOut(float out) {
+        this.out = out;
+    }
+
+    public ScalingControl getScaler() {
+        return scaler;
+    }
+
+    public void setScaler(ScalingControl scaler) {
+        this.scaler = scaler;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ShearingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ShearingGraphMousePlugin.java
new file mode 100644
index 0000000..e2d0b9a
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ShearingGraphMousePlugin.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.RenderingHints;
+import java.awt.Toolkit;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+import java.awt.image.BufferedImage;
+import java.util.Collections;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/** 
+ * ShearingGraphMousePlugin allows the user to drag with the mouse
+ * to shear the transform either in the horizontal or vertical direction.
+ * By default, the control or meta key must be depressed to activate
+ * shearing. 
+ * 
+ * 
+ * @author Tom Nelson
+ */
+public class ShearingGraphMousePlugin extends AbstractGraphMousePlugin
+    implements MouseListener, MouseMotionListener {
+
+    private static int mask = MouseEvent.CTRL_MASK;
+    
+    static {
+        if(System.getProperty("os.name").startsWith("Mac")) {
+            mask = MouseEvent.META_MASK;
+        }
+    }
+	/**
+	 * create an instance with default modifier values
+	 */
+	public ShearingGraphMousePlugin() {
+	    this(MouseEvent.BUTTON1_MASK | mask);
+	}
+
+	/**
+	 * create an instance with passed modifier values
+	 * @param modifiers the mouse modifiers to use
+	 */
+	public ShearingGraphMousePlugin(int modifiers) {
+	    super(modifiers);
+	    Dimension cd = Toolkit.getDefaultToolkit().getBestCursorSize(16,16);
+        BufferedImage cursorImage = 
+        		new BufferedImage(cd.width,cd.height,BufferedImage.TYPE_INT_ARGB);
+        Graphics g = cursorImage.createGraphics();
+        Graphics2D g2 = (Graphics2D)g;
+        g2.addRenderingHints(Collections.singletonMap(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
+        g.setColor(new Color(0,0,0,0));
+        g.fillRect(0,0,16,16);
+        
+        int left = 0;
+        int top = 0;
+        int right = 15;
+        int bottom = 15;
+        
+        g.setColor(Color.white);
+        g2.setStroke(new BasicStroke(3));
+        g.drawLine(left+2,top+5,right-2,top+5);
+        g.drawLine(left+2,bottom-5,right-2,bottom-5);
+        g.drawLine(left+2,top+5,left+4,top+3);
+        g.drawLine(left+2,top+5,left+4,top+7);
+        g.drawLine(right-2,bottom-5,right-4,bottom-3);
+        g.drawLine(right-2,bottom-5,right-4,bottom-7);
+
+        g.setColor(Color.black);
+        g2.setStroke(new BasicStroke(1));
+        g.drawLine(left+2,top+5,right-2,top+5);
+        g.drawLine(left+2,bottom-5,right-2,bottom-5);
+        g.drawLine(left+2,top+5,left+4,top+3);
+        g.drawLine(left+2,top+5,left+4,top+7);
+        g.drawLine(right-2,bottom-5,right-4,bottom-3);
+        g.drawLine(right-2,bottom-5,right-4,bottom-7);
+        g.dispose();
+        cursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(), "RotateCursor");
+	}
+
+	public void mousePressed(MouseEvent e) {
+	    VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+	    boolean accepted = checkModifiers(e);
+	    down = e.getPoint();
+	    if(accepted) {
+	        vv.setCursor(cursor);
+	    }
+	}
+    
+    public void mouseReleased(MouseEvent e) {
+        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+        down = null;
+        vv.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    }
+    
+    public void mouseDragged(MouseEvent e) {
+        if(down == null) return;
+        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+        boolean accepted = checkModifiers(e);
+        if(accepted) {
+            MutableTransformer modelTransformer = 
+            	vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+            vv.setCursor(cursor);
+            Point2D q = down;
+            Point2D p = e.getPoint();
+            float dx = (float) (p.getX()-q.getX());
+            float dy = (float) (p.getY()-q.getY());
+
+            Dimension d = vv.getSize();
+            float shx = 2.f*dx/d.height;
+            float shy = 2.f*dy/d.width;
+            Point2D center = vv.getCenter();
+            if(p.getX() < center.getX()) {
+                shy = -shy;
+            }
+            if(p.getY() < center.getY()) {
+                shx = -shx;
+            }
+            modelTransformer.shear(shx, shy, center);
+            down.x = e.getX();
+            down.y = e.getY();
+        
+            e.consume();
+        }
+    }
+
+    public void mouseClicked(MouseEvent e) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void mouseEntered(MouseEvent e) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void mouseExited(MouseEvent e) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void mouseMoved(MouseEvent e) {
+        // TODO Auto-generated method stub
+        
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SimpleEdgeSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SimpleEdgeSupport.java
new file mode 100644
index 0000000..279fd81
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SimpleEdgeSupport.java
@@ -0,0 +1,86 @@
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.geom.Point2D;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.BasicVisualizationServer;
+
+public class SimpleEdgeSupport<V,E> implements EdgeSupport<V,E> {
+
+	protected Point2D down;
+	protected EdgeEffects<V,E> edgeEffects;
+	protected EdgeType edgeType;
+	protected Supplier<E> edgeFactory;
+	protected V startVertex;
+	
+	public SimpleEdgeSupport(Supplier<E> edgeFactory) {
+		this.edgeFactory = edgeFactory;
+		this.edgeEffects = new CubicCurveEdgeEffects<V,E>();
+	}
+	
+//	@Override
+	public void startEdgeCreate(BasicVisualizationServer<V, E> vv,
+			V startVertex, Point2D startPoint, EdgeType edgeType) {
+		this.startVertex = startVertex;
+		this.down = startPoint;
+		this.edgeType = edgeType;
+		this.edgeEffects.startEdgeEffects(vv, startPoint, startPoint);
+		if(edgeType == EdgeType.DIRECTED) {
+			this.edgeEffects.startArrowEffects(vv, startPoint, startPoint);
+		}
+		vv.repaint();
+	}
+
+//	@Override
+	public void midEdgeCreate(BasicVisualizationServer<V, E> vv,
+			Point2D midPoint) {
+		if(startVertex != null) {
+			this.edgeEffects.midEdgeEffects(vv, down, midPoint);
+			if(this.edgeType == EdgeType.DIRECTED) {
+				this.edgeEffects.midArrowEffects(vv, down, midPoint);
+			}
+			vv.repaint();
+		}
+	}
+
+//	@Override
+	public void endEdgeCreate(BasicVisualizationServer<V, E> vv, V endVertex) {
+		if(startVertex != null) {
+			Graph<V,E> graph = vv.getGraphLayout().getGraph();
+			graph.addEdge(edgeFactory.get(), startVertex, endVertex, edgeType);
+			vv.repaint();
+		}
+		startVertex = null;
+		edgeType = EdgeType.UNDIRECTED;
+		edgeEffects.endEdgeEffects(vv);
+		edgeEffects.endArrowEffects(vv);
+	}
+
+	public EdgeEffects<V, E> getEdgeEffects() {
+		return edgeEffects;
+	}
+
+	public void setEdgeEffects(EdgeEffects<V, E> edgeEffects) {
+		this.edgeEffects = edgeEffects;
+	}
+
+	public EdgeType getEdgeType() {
+		return edgeType;
+	}
+
+	public void setEdgeType(EdgeType edgeType) {
+		this.edgeType = edgeType;
+	}
+
+	public Supplier<E> getEdgeFactory() {
+		return edgeFactory;
+	}
+
+	public void setEdgeFactory(Supplier<E> edgeFactory) {
+		this.edgeFactory = edgeFactory;
+	}
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SimpleVertexSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SimpleVertexSupport.java
new file mode 100644
index 0000000..da8da9e
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/SimpleVertexSupport.java
@@ -0,0 +1,56 @@
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.geom.Point2D;
+
+import com.google.common.base.Supplier;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.visualization.BasicVisualizationServer;
+
+/** 
+ * sample implementation showing how to use the VertexSupport interface member of the
+ * EditingGraphMousePlugin.
+ * override midVertexCreate and endVertexCreate for more elaborate implementations
+ * @author tanelso
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+public class SimpleVertexSupport<V,E> implements VertexSupport<V,E> {
+
+	protected Supplier<V> vertexFactory;
+	
+	public SimpleVertexSupport(Supplier<V> vertexFactory) {
+		this.vertexFactory = vertexFactory;
+	}
+	
+	public void startVertexCreate(BasicVisualizationServer<V, E> vv,
+			Point2D point) {
+		V newVertex = vertexFactory.get();
+		Layout<V,E> layout = vv.getGraphLayout();
+		Graph<V,E> graph = layout.getGraph();
+		graph.addVertex(newVertex);
+		layout.setLocation(newVertex, vv.getRenderContext().getMultiLayerTransformer().inverseTransform(point));
+		vv.repaint();
+	}
+
+	public void midVertexCreate(BasicVisualizationServer<V, E> vv,
+			Point2D point) {
+		// noop
+	}
+
+	public void endVertexCreate(BasicVisualizationServer<V, E> vv,
+			Point2D point) {
+		//noop
+	}
+
+	public Supplier<V> getVertexFactory() {
+		return vertexFactory;
+	}
+
+	public void setVertexFactory(Supplier<V> vertexFactory) {
+		this.vertexFactory = vertexFactory;
+	}
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/TranslatingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/TranslatingGraphMousePlugin.java
new file mode 100644
index 0000000..8f6e082
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/TranslatingGraphMousePlugin.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Cursor;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/** 
+ * TranslatingGraphMousePlugin uses a MouseButtonOne press and
+ * drag gesture to translate the graph display in the x and y
+ * direction. The default MouseButtonOne modifier can be overridden
+ * to cause a different mouse gesture to translate the display.
+ * 
+ * 
+ * @author Tom Nelson
+ */
+public class TranslatingGraphMousePlugin extends AbstractGraphMousePlugin
+    implements MouseListener, MouseMotionListener {
+
+	/**
+	 */
+	public TranslatingGraphMousePlugin() {
+	    this(MouseEvent.BUTTON1_MASK);
+	}
+
+	/**
+	 * create an instance with passed modifer value
+	 * @param modifiers the mouse event modifier to activate this function
+	 */
+	public TranslatingGraphMousePlugin(int modifiers) {
+	    super(modifiers);
+        this.cursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
+	}
+
+	/**
+     * Check the event modifiers. Set the 'down' point for later
+     * use. If this event satisfies the modifiers, change the cursor
+     * to the system 'move cursor'
+	 * @param e the event
+	 */
+	public void mousePressed(MouseEvent e) {
+	    VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+	    boolean accepted = checkModifiers(e);
+	    down = e.getPoint();
+	    if(accepted) {
+	        vv.setCursor(cursor);
+	    }
+	}
+    
+	/**
+	 * unset the 'down' point and change the cursoe back to the system
+     * default cursor
+	 */
+    public void mouseReleased(MouseEvent e) {
+        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+        down = null;
+        vv.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    }
+    
+    /**
+     * chack the modifiers. If accepted, translate the graph according
+     * to the dragging of the mouse pointer
+     * @param e the event
+	 */
+    public void mouseDragged(MouseEvent e) {
+        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
+        boolean accepted = checkModifiers(e);
+        if(accepted) {
+            MutableTransformer modelTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+            vv.setCursor(cursor);
+            try {
+                Point2D q = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(down);
+                Point2D p = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(e.getPoint());
+                float dx = (float) (p.getX()-q.getX());
+                float dy = (float) (p.getY()-q.getY());
+                
+                modelTransformer.translate(dx, dy);
+                down.x = e.getX();
+                down.y = e.getY();
+            } catch(RuntimeException ex) {
+                System.err.println("down = "+down+", e = "+e);
+                throw ex;
+            }
+        
+            e.consume();
+            vv.repaint();
+        }
+    }
+
+    public void mouseClicked(MouseEvent e) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void mouseEntered(MouseEvent e) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void mouseExited(MouseEvent e) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void mouseMoved(MouseEvent e) {
+        // TODO Auto-generated method stub
+        
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/VertexSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/VertexSupport.java
new file mode 100644
index 0000000..296921f
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/VertexSupport.java
@@ -0,0 +1,22 @@
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.BasicVisualizationServer;
+
+/**
+ * interface to support the creation of new vertices by the EditingGraphMousePlugin.
+ * SimpleVertexSupport is a sample implementation.
+ * @author tanelso
+ *
+ * @param <V> the vertex type
+ */
+public interface VertexSupport<V,E> {
+	
+	void startVertexCreate(BasicVisualizationServer<V,E> vv, Point2D point);
+	
+	void midVertexCreate(BasicVisualizationServer<V,E> vv, Point2D point);
+	
+	void endVertexCreate(BasicVisualizationServer<V,E> vv, Point2D point);
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ViewScalingControl.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ViewScalingControl.java
new file mode 100644
index 0000000..3b651a2
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ViewScalingControl.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/** 
+ * ViewScalingGraphMouse applies a scaling transform to the View
+ * of the graph. This causes all elements of the graph to grow
+ * larger or smaller. ViewScalingGraphMouse, by default, is activated
+ * by the MouseWheel when the control key is pressed. The control
+ * key modifier can be overridden in the contstructor.
+ * 
+ * @author Tom Nelson
+ */
+public class ViewScalingControl implements ScalingControl {
+
+	/**
+	 * zoom the display in or out, depending on the direction of the
+	 * mouse wheel motion.
+	 */
+    public void scale(VisualizationServer<?, ?> vv, float amount, Point2D from) {
+        MutableTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
+        viewTransformer.scale(amount, amount, from);
+        vv.repaint();
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ViewTranslatingGraphMousePlugin.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ViewTranslatingGraphMousePlugin.java
new file mode 100644
index 0000000..2b2c4d4
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/ViewTranslatingGraphMousePlugin.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 8, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.Cursor;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/** 
+ * ViewTranslatingGraphMousePlugin uses a MouseButtonOne press and
+ * drag gesture to translate the graph display in the x and y
+ * direction by changing the AffineTransform applied to the Graphics2D.
+ * The default MouseButtonOne modifier can be overridden
+ * to cause a different mouse gesture to translate the display.
+ * 
+ * 
+ * @author Tom Nelson
+ */
+public class ViewTranslatingGraphMousePlugin extends AbstractGraphMousePlugin
+    implements MouseListener, MouseMotionListener {
+
+	/**
+	 */
+	public ViewTranslatingGraphMousePlugin() {
+	    this(MouseEvent.BUTTON1_MASK);
+	}
+
+	/**
+	 * create an instance with passed modifer value
+	 * @param modifiers the mouse event modifier to activate this function
+	 */
+	public ViewTranslatingGraphMousePlugin(int modifiers) {
+	    super(modifiers);
+        this.cursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
+	}
+
+	/**
+     * Check the event modifiers. Set the 'down' point for later
+     * use. If this event satisfies the modifiers, change the cursor
+     * to the system 'move cursor'
+	 * @param e the event
+	 */
+	public void mousePressed(MouseEvent e) {
+	    VisualizationViewer<?,?> vv = (VisualizationViewer<?,?>)e.getSource();
+	    boolean accepted = checkModifiers(e);
+	    down = e.getPoint();
+	    if(accepted) {
+	        vv.setCursor(cursor);
+	    }
+	}
+    
+	/**
+	 * unset the 'down' point and change the cursoe back to the system
+     * default cursor
+	 */
+    public void mouseReleased(MouseEvent e) {
+        VisualizationViewer<?,?> vv = (VisualizationViewer<?,?>)e.getSource();
+        down = null;
+        vv.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    }
+    
+    /**
+     * chack the modifiers. If accepted, translate the graph according
+     * to the dragging of the mouse pointer
+     * @param e the event
+	 */
+    public void mouseDragged(MouseEvent e) {
+        VisualizationViewer<?,?> vv = (VisualizationViewer<?,?>)e.getSource();
+        boolean accepted = checkModifiers(e);
+        if(accepted) {
+            MutableTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
+            vv.setCursor(cursor);
+            try {
+                Point2D q = viewTransformer.inverseTransform(down);
+                Point2D p = viewTransformer.inverseTransform(e.getPoint());
+                float dx = (float) (p.getX()-q.getX());
+                float dy = (float) (p.getY()-q.getY());
+                
+                viewTransformer.translate(dx, dy);
+                down.x = e.getX();
+                down.y = e.getY();
+            } catch(RuntimeException ex) {
+                System.err.println("down = "+down+", e = "+e);
+                throw ex;
+            }
+        
+            e.consume();
+        }
+    }
+
+    public void mouseClicked(MouseEvent e) {
+    }
+
+    public void mouseEntered(MouseEvent e) {
+    }
+
+    public void mouseExited(MouseEvent e) {
+    }
+
+    public void mouseMoved(MouseEvent e) {
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/package.html b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/package.html
new file mode 100644
index 0000000..3b490af
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/control/package.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Mechanisms for manipulating and controlling a graph visualization, largely
+in terms of mouse plugins. 
+
+</body>
+</html>
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/AbstractVertexShapeTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/AbstractVertexShapeTransformer.java
new file mode 100644
index 0000000..d74a7f2
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/AbstractVertexShapeTransformer.java
@@ -0,0 +1,57 @@
+/*
+ * Created on Jul 16, 2004
+ *
+ * Copyright (c) 2004, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.visualization.decorators;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+import edu.uci.ics.jung.visualization.util.VertexShapeFactory;
+
+
+
+/**
+ * 
+ * @author Joshua O'Madadhain
+ */
+public abstract class AbstractVertexShapeTransformer<V> implements SettableVertexShapeTransformer<V>
+{
+    protected Function<? super V,Integer> vsf;
+    protected Function<? super V,Float> varf;
+    protected VertexShapeFactory<V> factory;
+    public final static int DEFAULT_SIZE = 8;
+    public final static float DEFAULT_ASPECT_RATIO = 1.0f;
+    
+    public AbstractVertexShapeTransformer(Function<? super V,Integer> vsf, Function<? super V,Float> varf)
+    {
+        this.vsf = vsf;
+        this.varf = varf;
+        factory = new VertexShapeFactory<V>(vsf, varf);
+    }
+
+	public AbstractVertexShapeTransformer()
+    {
+        this(Functions.constant(DEFAULT_SIZE), 
+                Functions.constant(DEFAULT_ASPECT_RATIO));
+    }
+    
+    public void setSizeTransformer(Function<V,Integer> vsf)
+    {
+        this.vsf = vsf;
+        factory = new VertexShapeFactory<V>(vsf, varf);
+    }
+    
+    public void setAspectRatioTransformer(Function<V,Float> varf)
+    {
+        this.varf = varf;
+        factory = new VertexShapeFactory<V>(vsf, varf);
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/ConstantDirectionalEdgeValueTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/ConstantDirectionalEdgeValueTransformer.java
new file mode 100644
index 0000000..08f9a32
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/ConstantDirectionalEdgeValueTransformer.java
@@ -0,0 +1,68 @@
+/*
+ * Created on Oct 21, 2004
+ *
+ * Copyright (c) 2004, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.visualization.decorators;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.graph.util.EdgeType;
+
+
+/**
+ * Returns the constructor-specified value for each edge type.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class ConstantDirectionalEdgeValueTransformer<V,E> implements Function<Context<Graph<V,E>,E>,Number>
+{
+    protected Double undirected_value;
+    protected Double directed_value;
+
+    /**
+     * 
+     * @param undirected the value to return if the edge is undirected
+     * @param directed the value to return if the edge is directed
+     */
+    public ConstantDirectionalEdgeValueTransformer(double undirected, double directed)
+    {
+        this.undirected_value = new Double(undirected);
+        this.directed_value = new Double(directed);
+    }
+    
+    public Number apply(Context<Graph<V,E>,E> context) {
+    	Graph<V,E> graph = context.graph;
+    	E e = context.element;
+        if (graph.getEdgeType(e) == EdgeType.DIRECTED)
+            return directed_value;
+        else 
+            return undirected_value;
+    }
+    
+    /**
+     * Sets the value returned for undirected edges to <code>value</code>.
+     * @param value the new value to return for undirected edges
+     */
+    public void setUndirectedValue(double value)
+    {
+    	this.undirected_value = value;
+    }
+    
+    /**
+     * Sets the value returned for directed edges to <code>value</code>.
+     * @param value the new value to return for directed edges
+     */
+    public void setDirectedValue(double value)
+    {
+    	this.directed_value = value;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/DirectionalEdgeArrowTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/DirectionalEdgeArrowTransformer.java
new file mode 100644
index 0000000..c6418cc
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/DirectionalEdgeArrowTransformer.java
@@ -0,0 +1,50 @@
+/*
+ * Created on Jul 18, 2004
+ *
+ * Copyright (c) 2004, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.visualization.decorators;
+
+import java.awt.Shape;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.util.ArrowFactory;
+
+/**
+ * Returns wedge arrows for undirected edges and notched arrows
+ * for directed edges, of the specified dimensions.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class DirectionalEdgeArrowTransformer<V,E> implements Function<Context<Graph<V,E>,E>,Shape> {
+    protected Shape undirected_arrow;
+    protected Shape directed_arrow;
+    
+    public DirectionalEdgeArrowTransformer(int length, int width, int notch_depth)
+    {
+        directed_arrow = ArrowFactory.getNotchedArrow(width, length, notch_depth);
+        undirected_arrow = ArrowFactory.getWedgeArrow(width, length);
+    }
+    
+    /**
+     * 
+     */
+    public Shape apply(Context<Graph<V,E>,E> context)
+    {
+        if (context.graph.getEdgeType(context.element) == EdgeType.DIRECTED)
+            return directed_arrow;
+        else 
+            return undirected_arrow;
+    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/EdgeShape.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/EdgeShape.java
new file mode 100644
index 0000000..1e79f6e
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/EdgeShape.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ * Created on March 10, 2005
+ */
+package edu.uci.ics.jung.visualization.decorators;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.CubicCurve2D;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.geom.QuadCurve2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RectangularShape;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.EdgeIndexFunction;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.visualization.util.ArrowFactory;
+
+
+/**
+ * An interface for decorators that return a 
+ * <code>Shape</code> for a specified edge.
+ * 
+ * All edge shapes must be defined so that their endpoints are at
+ * (0,0) and (1,0). They will be scaled, rotated and translated into
+ * position by the PluggableRenderer.
+ *  
+ * @author Tom Nelson
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+public class EdgeShape<V,E> {
+    private static final Line2D LINE = new Line2D.Float(0.0f, 0.0f, 1.0f, 0.0f);
+    private static final GeneralPath BENT_LINE = new GeneralPath();
+    private static final QuadCurve2D QUAD_CURVE = new QuadCurve2D.Float();
+    private static final CubicCurve2D CUBIC_CURVE = new CubicCurve2D.Float();
+    private static final Ellipse2D ELLIPSE = new Ellipse2D.Float(-.5f, -.5f, 1, 1);
+    private static Rectangle2D BOX = new Rectangle2D.Float();
+
+    private static GeneralPath triangle;
+    private static GeneralPath bowtie;
+
+	protected final Graph<V, E> graph;
+	
+    /**
+     * A convenience instance for other edge shapes to use for self-loop edges 
+     * where parallel instances will not overlay each other.
+     */
+    protected final Loop loop;
+    
+    /**
+     * A convenience instance for other edge shapes to use for self-loop edges
+     * where parallel instances overlay each other.
+     */
+    protected final SimpleLoop simpleLoop;
+
+    protected final Box box;
+
+	public EdgeShape(Graph<V, E> g) {
+		this.graph = g;
+		this.box = new Box();
+		this.loop = new Loop();
+		this.simpleLoop = new SimpleLoop();
+	}
+	
+	private Shape getLoopOrNull(E e) {
+		return getLoopOrNull(e, loop);
+	}
+	
+	private Shape getLoopOrNull(E e, Function<? super E, Shape> loop) {
+        Pair<V> endpoints = graph.getEndpoints(e);
+        checkNotNull(endpoints);
+    	boolean isLoop = endpoints.getFirst().equals(endpoints.getSecond());
+    	if (isLoop) {
+    		return loop.apply(e);
+    	}
+        return null;
+	}
+	
+	public static <V, E> EdgeShape<V, E>.Line line(Graph<V, E> graph) {
+        return new EdgeShape<V, E>(graph).new Line();
+	}
+	
+	public static <V, E> EdgeShape<V, E>.QuadCurve quadCurve(Graph<V, E> graph) {
+        return new EdgeShape<V, E>(graph).new QuadCurve();
+	}
+	
+	public static <V, E> EdgeShape<V, E>.QuadCurve cubicCurve(Graph<V, E> graph) {
+        return new EdgeShape<V, E>(graph).new QuadCurve();
+	}
+	
+	public static <V, E> EdgeShape<V, E>.Orthogonal orthogonal(Graph<V, E> graph) {
+		return new EdgeShape<V, E>(graph).new Orthogonal();
+	}
+	
+	public static <V, E> EdgeShape<V, E>.Wedge wedge(Graph<V, E> graph, int width) {
+		return new EdgeShape<V, E>(graph).new Wedge(width);
+	}
+	
+    /**
+     * An edge shape that renders as a straight line between
+     * the vertex endpoints.
+     */
+    public class Line implements Function<E, Shape> {
+        /**
+         * Get the shape for this edge, returning either the
+         * shared instance or, in the case of self-loop edges, the 
+         * Loop shared instance.
+         */
+		public Shape apply(E e) {
+			Shape loop = getLoopOrNull(e);
+			return loop == null
+					? LINE
+					: loop;
+        }
+    }
+
+    private int getIndex(E e, EdgeIndexFunction<V, E> edgeIndexFunction) {
+    	return edgeIndexFunction == null
+    			? 1
+    			: edgeIndexFunction.getIndex(graph, e);
+    }
+    
+    /**
+     * An edge shape that renders as a bent-line between the
+     * vertex endpoints.
+     */
+    public class BentLine extends ParallelEdgeShapeTransformer<V,E> {
+		public void setEdgeIndexFunction(EdgeIndexFunction<V,E> edgeIndexFunction) {
+			this.edgeIndexFunction = edgeIndexFunction;
+            loop.setEdgeIndexFunction(edgeIndexFunction);
+        }
+
+		/**
+         * Get the shape for this edge, returning either the
+         * shared instance or, in the case of self-loop edges, the
+         * Loop shared instance.
+         */
+		public Shape apply(E e) {
+        	Shape edgeShape = getLoopOrNull(e);
+        	if (edgeShape != null) {
+        		return edgeShape;
+        	}
+
+        	int index = getIndex(e, edgeIndexFunction);
+            float controlY = control_offset_increment + control_offset_increment * index;
+            BENT_LINE.reset();
+            BENT_LINE.moveTo(0.0f, 0.0f);
+            BENT_LINE.lineTo(0.5f, controlY);
+            BENT_LINE.lineTo(1.0f, 1.0f);
+            return BENT_LINE;
+        }
+
+    }
+    
+    /**
+     * An edge shape that renders as a QuadCurve between vertex
+     * endpoints.
+     */
+    public class QuadCurve extends ParallelEdgeShapeTransformer<V,E> {
+    	@Override
+		public void setEdgeIndexFunction(EdgeIndexFunction<V,E> parallelEdgeIndexFunction) {
+            this.edgeIndexFunction = parallelEdgeIndexFunction;
+            loop.setEdgeIndexFunction(parallelEdgeIndexFunction);
+        }
+
+    	/**
+         * Get the shape for this edge, returning either the
+         * shared instance or, in the case of self-loop edges, the
+         * Loop shared instance.
+         */
+		public Shape apply(E e) {
+        	Shape edgeShape = getLoopOrNull(e);
+        	if (edgeShape != null) {
+        		return edgeShape;
+        	}
+            
+            int index = getIndex(e, edgeIndexFunction);
+            
+            float controlY = control_offset_increment + 
+                control_offset_increment * index;
+            QUAD_CURVE.setCurve(0.0f, 0.0f, 0.5f, controlY, 1.0f, 0.0f);
+            return QUAD_CURVE;
+        }
+    }
+    
+    /**
+     * An edge shape that renders as a CubicCurve between vertex
+     * endpoints.  The two control points are at 
+     * (1/3*length, 2*controlY) and (2/3*length, controlY)
+     * giving a 'spiral' effect.
+     */
+    public class CubicCurve extends ParallelEdgeShapeTransformer<V,E> {
+		public void setEdgeIndexFunction(EdgeIndexFunction<V,E> edgeIndexFunction) {
+            this.edgeIndexFunction = edgeIndexFunction;
+            loop.setEdgeIndexFunction(edgeIndexFunction);
+       }
+
+		/**
+         * Get the shape for this edge, returning either the
+         * shared instance or, in the case of self-loop edges, the
+         * Loop shared instance.
+         */
+		public Shape apply(E e) {
+        	Shape edgeShape = getLoopOrNull(e);
+        	if (edgeShape != null) {
+        		return edgeShape;
+        	}
+            
+            int index = getIndex(e, edgeIndexFunction);
+
+			float controlY = control_offset_increment
+			    + control_offset_increment * index;
+			CUBIC_CURVE.setCurve(0.0f, 0.0f, 0.33f, 2 * controlY, .66f, -controlY,
+					1.0f, 0.0f);
+            return CUBIC_CURVE;
+        }
+    }
+    
+    /**
+	 * An edge shape that renders as a loop with its nadir at the center of the
+	 * vertex. Parallel instances will overlap.
+	 * 
+     * @author Tom Nelson 
+     */
+    public class SimpleLoop extends ParallelEdgeShapeTransformer<V,E> {
+        public Shape apply(E e) {
+            return ELLIPSE;
+        }
+    }
+    
+    private Shape buildFrame(RectangularShape shape, int index) {
+        float x = -.5f;
+        float y = -.5f;
+        float diam = 1.f;
+        diam += diam * index/2;
+        x += x * index/2;
+        y += y * index/2;
+    	
+        shape.setFrame(x, y, diam, diam);
+        
+        return shape;
+    }
+    
+    /**
+     * An edge shape that renders as a loop with its nadir at the
+     * center of the vertex. Parallel instances will not overlap.
+     */
+    public class Loop extends ParallelEdgeShapeTransformer<V,E> {
+        public Shape apply(E e) {
+            return buildFrame(ELLIPSE, getIndex(e, edgeIndexFunction));
+        }
+    }
+
+    /**
+     * An edge shape that renders as an isosceles triangle whose
+     * apex is at the destination vertex for directed edges,
+     * and as a "bowtie" shape for undirected edges.
+     * @author Joshua O'Madadhain
+     */
+    public class Wedge extends ParallelEdgeShapeTransformer<V,E> {
+        public Wedge(int width)  {
+            triangle = ArrowFactory.getWedgeArrow(width, 1);
+            triangle.transform(AffineTransform.getTranslateInstance(1,0));
+            bowtie = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+            bowtie.moveTo(0, width/2);
+            bowtie.lineTo(1, -width/2);
+            bowtie.lineTo(1, width/2);
+            bowtie.lineTo(0, -width/2);
+            bowtie.closePath();
+        }
+        
+        public Shape apply(E e) {
+        	Shape edgeShape = getLoopOrNull(e);
+        	if (edgeShape != null) {
+        		return edgeShape;
+        	}
+        	return (graph.getEdgeType(e) == EdgeType.DIRECTED)
+        			? triangle
+        			: bowtie;
+        }
+    }
+    
+    /**
+     * An edge shape that renders as a diamond with its nadir at the
+     * center of the vertex. Parallel instances will not overlap.
+     */
+    public class Box extends ParallelEdgeShapeTransformer<V,E> {
+        public Shape apply(E e) {
+            return buildFrame(BOX, getIndex(e, edgeIndexFunction));
+        }
+    }
+
+
+    /**
+     * An edge shape that renders as a bent-line between the vertex endpoints.
+     */
+    public class Orthogonal extends ParallelEdgeShapeTransformer<V,E> {
+		public Shape apply(E e) {
+			Shape loop = getLoopOrNull(e, box);
+			return loop == null
+					? LINE
+					: loop;
+        }
+    }
+}
+    
+
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/EllipseVertexShapeTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/EllipseVertexShapeTransformer.java
new file mode 100644
index 0000000..e5b6445
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/EllipseVertexShapeTransformer.java
@@ -0,0 +1,37 @@
+/*
+ * Created on Jul 16, 2004
+ *
+ * Copyright (c) 2004, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.visualization.decorators;
+
+import java.awt.Shape;
+
+import com.google.common.base.Function;
+
+/**
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class EllipseVertexShapeTransformer<V> extends AbstractVertexShapeTransformer<V>
+	implements Function<V,Shape>
+{
+    public EllipseVertexShapeTransformer() 
+    {
+    }
+    public EllipseVertexShapeTransformer(Function<V,Integer> vsf, Function<V,Float> varf)
+    {
+        super(vsf, varf);
+    }
+    
+    public Shape apply(V v)
+    {
+        return factory.getEllipse(v);
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/GradientEdgePaintTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/GradientEdgePaintTransformer.java
new file mode 100644
index 0000000..3220bd1
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/GradientEdgePaintTransformer.java
@@ -0,0 +1,107 @@
+/*
+ * Created on Apr 8, 2005
+ *
+ * Copyright (c) 2004, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.visualization.decorators;
+
+import java.awt.Color;
+import java.awt.GradientPaint;
+import java.awt.Paint;
+import java.awt.geom.Point2D;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.util.SelfLoopEdgePredicate;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer;
+
+/**
+ * Creates <code>GradientPaint</code> instances which can be used
+ * to paint an <code>Edge</code>.  For <code>DirectedEdge</code>s, 
+ * the color will blend from <code>c1</code> (source) to 
+ * <code>c2</code> (destination); for <code>UndirectedEdge</code>s,
+ * the color will be <code>c1</code> at each end and <code>c2</code>
+ * in the middle.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class GradientEdgePaintTransformer<V, E> 
+	implements Function<E,Paint>
+{
+    protected Color c1;
+    protected Color c2;
+    protected VisualizationViewer<V,E> vv;
+    protected BidirectionalTransformer transformer;
+    protected Predicate<Context<Graph<V,E>,E>> selfLoop = new SelfLoopEdgePredicate<V,E>();
+
+    public GradientEdgePaintTransformer(Color c1, Color c2, 
+            VisualizationViewer<V,E> vv)
+    {
+        this.c1 = c1;
+        this.c2 = c2;
+        this.vv = vv;
+        this.transformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
+    }
+    
+    public Paint apply(E e)
+    {
+        Layout<V, E> layout = vv.getGraphLayout();
+        Pair<V> p = layout.getGraph().getEndpoints(e);
+        V b = p.getFirst();
+        V f = p.getSecond();
+        Point2D pb = transformer.transform(layout.apply(b));
+        Point2D pf = transformer.transform(layout.apply(f));
+        float xB = (float) pb.getX();
+        float yB = (float) pb.getY();
+        float xF = (float) pf.getX();
+        float yF = (float) pf.getY();
+        if ((layout.getGraph().getEdgeType(e)) == EdgeType.UNDIRECTED)  {
+            xF = (xF + xB) / 2;
+            yF = (yF + yB) / 2;
+        } 
+        if(selfLoop.apply(Context.<Graph<V,E>,E>getInstance(layout.getGraph(), e))) {
+        	yF += 50;
+        	xF += 50;
+        }
+
+        return new GradientPaint(xB, yB, getColor1(e), xF, yF, getColor2(e), true);
+    }
+    
+    /**
+     * Returns <code>c1</code>.  Subclasses may override
+     * this method to enable more complex behavior (e.g., for
+     * picked edges).
+     * @param e the edge for which a color is to be retrieved
+     * @return the constructor-supplied color {@code c1}
+     */
+    protected Color getColor1(E e)
+    {
+        return c1;
+    }
+
+    /**
+     * Returns <code>c2</code>.  Subclasses may override
+     * this method to enable more complex behavior (e.g., for
+     * picked edges).
+     * @param e the edge for which a color is to be retrieved
+     * @return the constructor-supplied color {@code c2}
+     */
+    protected Color getColor2(E e)
+    {
+        return c2;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/InterpolatingVertexSizeTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/InterpolatingVertexSizeTransformer.java
new file mode 100644
index 0000000..c6c92e5
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/InterpolatingVertexSizeTransformer.java
@@ -0,0 +1,72 @@
+/*
+ * Created on Nov 3, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.visualization.decorators;
+
+import com.google.common.base.Function;
+
+
+/**
+ * Provides vertex sizes that are spaced proportionally between 
+ * min_size and max_size depending on 
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class InterpolatingVertexSizeTransformer<V> implements Function<V,Integer>
+{
+    protected double min;
+    protected double max;
+    protected Function<V, ? extends Number> values;
+    protected int min_size;
+    protected int size_diff;
+    
+    public InterpolatingVertexSizeTransformer(Function<V, ? extends Number> values, 
+            int min_size, int max_size)
+    {
+        super();
+        if (min_size < 0 || max_size < 0)
+            throw new IllegalArgumentException("sizes must be non-negative");
+        if (min_size > max_size)
+            throw new IllegalArgumentException("min_size must be <= max_size");
+        this.min = 0;
+        this.max = 0;
+        this.values = values;
+        setMinSize(min_size);
+        setMaxSize(max_size);
+    }
+
+    public Integer apply(V v)
+    {
+        Number n = values.apply(v);
+        double value = min;
+        if (n != null)
+            value = n.doubleValue();
+        min = Math.min(this.min, value);
+        max = Math.max(this.max, value);
+        
+        if (min == max)
+            return min_size;
+        
+        // interpolate between min and max sizes based on how big value is 
+        // with respect to min and max values
+        return min_size + (int)(((value - min) / (max - min)) * size_diff);
+    }
+    
+    public void setMinSize(int min_size)
+    {
+        this.min_size = min_size;
+    }
+
+    public void setMaxSize(int max_size)
+    {
+        this.size_diff = max_size - this.min_size;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/NumberFormattingTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/NumberFormattingTransformer.java
new file mode 100644
index 0000000..8dad0b8
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/NumberFormattingTransformer.java
@@ -0,0 +1,42 @@
+/*
+ * Created on Feb 16, 2009
+ *
+ * Copyright (c) 2009, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.visualization.decorators;
+
+import java.text.NumberFormat;
+
+import com.google.common.base.Function;
+
+/**
+ * Transforms inputs to String representations by chaining an input 
+ * {@code Number}-generating {@code Function} with an internal 
+ * {@code NumberFormat} instance.
+ * @author Joshua O'Madadhain
+ */
+public class NumberFormattingTransformer<T> implements Function<T, String>
+{
+    private Function<T, ? extends Number> values;
+    private NumberFormat formatter = NumberFormat.getInstance();
+    
+    public NumberFormattingTransformer(Function<T, ? extends Number> values)
+    {
+        this.values = values;
+    }
+
+    /**
+     * Returns a formatted string for the input.
+     */
+    public String apply(T input)
+    {
+        return formatter.format(values.apply(input));
+    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/ParallelEdgeShapeTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/ParallelEdgeShapeTransformer.java
new file mode 100644
index 0000000..0aa08f2
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/ParallelEdgeShapeTransformer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ * Created on March 10, 2005
+ */
+package edu.uci.ics.jung.visualization.decorators;
+
+import java.awt.Shape;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.graph.util.EdgeIndexFunction;
+
+
+/**
+ * An abstract class for edge-to-Shape functions that work with parallel edges.
+ *  
+ * @author Tom Nelson
+ */
+public abstract class ParallelEdgeShapeTransformer<V,E> implements Function<E, Shape> {
+    /** Specifies the distance between control points for edges being drawn in parallel. */
+    protected float control_offset_increment = 20.f;
+    protected EdgeIndexFunction<V,E> edgeIndexFunction;
+    
+    public void setControlOffsetIncrement(float y) {
+        control_offset_increment = y;
+    }
+    
+    public void setEdgeIndexFunction(EdgeIndexFunction<V,E> edgeIndexFunction) {
+    	this.edgeIndexFunction = edgeIndexFunction;
+    }
+    
+    public EdgeIndexFunction<V,E> getEdgeIndexFunction() {
+    	return edgeIndexFunction;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/PickableEdgePaintTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/PickableEdgePaintTransformer.java
new file mode 100644
index 0000000..9998ae8
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/PickableEdgePaintTransformer.java
@@ -0,0 +1,59 @@
+/*
+ * Created on Mar 10, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.visualization.decorators;
+
+import java.awt.Paint;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.visualization.picking.PickedInfo;
+
+/**
+ * Paints each edge according to the <code>Paint</code>
+ * parameters given in the constructor, so that picked and
+ * non-picked edges can be made to look different.
+ * 
+ * @author Tom Nelson 
+ * @author Joshua O'Madadhain
+ * 
+ */
+public class PickableEdgePaintTransformer<E> implements Function<E,Paint> {
+    protected PickedInfo<E> pi;
+    protected Paint draw_paint;
+    protected Paint picked_paint;
+
+    /**
+     * 
+     * @param pi            specifies which vertices report as "picked"
+     * @param draw_paint    <code>Paint</code> used to draw edge shapes
+     * @param picked_paint  <code>Paint</code> used to draw picked edge shapes
+     */
+    public PickableEdgePaintTransformer(PickedInfo<E> pi, Paint draw_paint, Paint picked_paint) {
+        if (pi == null)
+            throw new IllegalArgumentException("PickedInfo instance must be non-null");
+        this.pi = pi;
+        this.draw_paint = draw_paint;
+        this.picked_paint = picked_paint;
+    }
+    
+    /**
+     * 
+     */
+    public Paint apply(E e) {
+        if (pi.isPicked(e)) {
+            return picked_paint;
+        }
+        else {
+            return draw_paint;
+        }
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/PickableVertexIconTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/PickableVertexIconTransformer.java
new file mode 100644
index 0000000..9c1291c
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/PickableVertexIconTransformer.java
@@ -0,0 +1,55 @@
+/*
+* Created on Mar 10, 2005
+*
+* Copyright (c) 2005, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.visualization.decorators;
+
+import javax.swing.Icon;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.visualization.picking.PickedInfo;
+
+/**
+ * Supplies an Icon for each vertex according to the <code>Icon</code>
+ * parameters given in the constructor, so that picked and
+ * non-picked vertices can be made to look different.
+ */
+public class PickableVertexIconTransformer<V> implements Function<V,Icon> {
+
+    protected Icon icon;
+    protected Icon picked_icon;
+    protected PickedInfo<V> pi;
+    
+    /**
+     * 
+     * @param pi            specifies which vertices report as "picked"
+     * @param icon    <code>Icon</code> used to represent vertices
+     * @param picked_icon  <code>Icon</code> used to represent picked vertices
+     */
+    public PickableVertexIconTransformer(PickedInfo<V> pi, Icon icon, Icon picked_icon)
+    {
+        if (pi == null)
+            throw new IllegalArgumentException("PickedInfo instance must be non-null");
+        this.pi = pi;
+        this.icon = icon;
+        this.picked_icon = picked_icon;
+    }
+
+    /**
+     * Returns the appropriate <code>Icon</code>, depending on picked state.
+     */
+	public Icon apply(V v) {
+        if (pi.isPicked(v))
+            return picked_icon;
+        else
+            return icon;
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/PickableVertexPaintTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/PickableVertexPaintTransformer.java
new file mode 100644
index 0000000..14d4b6d
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/PickableVertexPaintTransformer.java
@@ -0,0 +1,55 @@
+/*
+* Created on Mar 10, 2005
+*
+* Copyright (c) 2005, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.visualization.decorators;
+
+import java.awt.Paint;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.visualization.picking.PickedInfo;
+
+/**
+ * Paints each vertex according to the <code>Paint</code>
+ * parameters given in the constructor, so that picked and
+ * non-picked vertices can be made to look different.
+ */
+public class PickableVertexPaintTransformer<V> implements Function<V,Paint> {
+
+    protected Paint fill_paint;
+    protected Paint picked_paint;
+    protected PickedInfo<V> pi;
+    
+    /**
+     * 
+     * @param pi            specifies which vertices report as "picked"
+     * @param fill_paint    <code>Paint</code> used to fill vertex shapes
+     * @param picked_paint  <code>Paint</code> used to fill picked vertex shapes
+     */
+    public PickableVertexPaintTransformer(PickedInfo<V> pi, 
+    		Paint fill_paint, Paint picked_paint)
+    {
+        if (pi == null)
+            throw new IllegalArgumentException("PickedInfo instance must be non-null");
+        this.pi = pi;
+        this.fill_paint = fill_paint;
+        this.picked_paint = picked_paint;
+    }
+
+    public Paint apply(V v)
+    {
+        if (pi.isPicked(v))
+            return picked_paint;
+        else
+            return fill_paint;
+    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/SettableVertexShapeTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/SettableVertexShapeTransformer.java
new file mode 100644
index 0000000..5a1d9af
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/SettableVertexShapeTransformer.java
@@ -0,0 +1,29 @@
+/*
+ * Created on Jul 18, 2004
+ *
+ * Copyright (c) 2004, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.visualization.decorators;
+
+import java.awt.Shape;
+
+import com.google.common.base.Function;
+
+
+
+/**
+ * 
+ * @author Joshua O'Madadhain
+ */
+public interface SettableVertexShapeTransformer<V> extends Function<V,Shape>
+{
+    public abstract void setSizeTransformer(Function<V,Integer> vsf);
+
+    public abstract void setAspectRatioTransformer(Function<V,Float> varf);
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/ToStringLabeller.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/ToStringLabeller.java
new file mode 100644
index 0000000..9dac59b
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/ToStringLabeller.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Apr 13, 2004
+ */
+package edu.uci.ics.jung.visualization.decorators;
+
+import com.google.common.base.Function;
+
+
+
+/**
+ * Labels vertices by their toString. This class functions as a drop-in
+ * replacement for the default StringLabeller method. This class does not
+ * guarantee unique labels; or even consistent ones.
+ * 
+ * @author danyelf
+ */
+public class ToStringLabeller implements Function<Object, String> {
+
+    /**
+     * @return o.toString()
+     */
+    public String apply(Object o) {
+        return o.toString();
+    }
+
+ }
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/VertexIconShapeTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/VertexIconShapeTransformer.java
new file mode 100644
index 0000000..56a3cdd
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/VertexIconShapeTransformer.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 1, 2005
+ */
+
+package edu.uci.ics.jung.visualization.decorators;
+
+import java.awt.Image;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.visualization.util.ImageShapeUtils;
+
+/**
+ * A default implementation that stores images in a Map keyed on the
+ * vertex. Also applies a shaping function to images to extract the
+ * shape of the opaque part of a transparent image.
+ * 
+ * @author Tom Nelson 
+ */
+public class VertexIconShapeTransformer<V> implements Function<V,Shape> {
+	protected Map<Image, Shape> shapeMap = new HashMap<Image, Shape>();
+	protected Map<V, Icon> iconMap;
+	protected Function<V, Shape> delegate;
+     
+	/**
+	 * Creates an instance with the specified delegate.
+	 * @param delegate the vertex-to-shape function to use if no image is present for the vertex
+	 */
+    public VertexIconShapeTransformer(Function<V, Shape> delegate) {
+        this.delegate = delegate;
+    }
+
+    /**
+     * @return Returns the delegate.
+     */
+    public Function<V,Shape> getDelegate() {
+        return delegate;
+    }
+
+    /**
+     * @param delegate The delegate to set.
+     */
+    public void setDelegate(Function<V,Shape> delegate) {
+        this.delegate = delegate;
+    }
+
+    /**
+     * get the shape from the image. If not available, get
+     * the shape from the delegate VertexShapeFunction
+     */
+    public Shape apply(V v) {
+		Icon icon = iconMap.get(v);
+		if (icon != null && icon instanceof ImageIcon) {
+			Image image = ((ImageIcon) icon).getImage();
+			Shape shape = (Shape) shapeMap.get(image);
+			if (shape == null) {
+			    shape = ImageShapeUtils.getShape(image, 30);
+			    if(shape.getBounds().getWidth() > 0 && 
+			            shape.getBounds().getHeight() > 0) {
+                    // don't cache a zero-sized shape, wait for the image
+			       // to be ready
+                    int width = image.getWidth(null);
+                    int height = image.getHeight(null);
+                    AffineTransform transform = AffineTransform
+						.getTranslateInstance(-width / 2, -height / 2);
+                    shape = transform.createTransformedShape(shape);
+                    shapeMap.put(image, shape);
+                }
+			}
+			return shape;
+		} else {
+			return delegate.apply(v);
+		}
+	}
+
+    /**
+	 * @return the iconMap
+	 */
+	public Map<V, Icon> getIconMap() {
+		return iconMap;
+	}
+
+	/**
+	 * @param iconMap the iconMap to set
+	 */
+	public void setIconMap(Map<V, Icon> iconMap) {
+		this.iconMap = iconMap;
+	}
+
+	/**
+	 * @return the shapeMap
+	 */
+	public Map<Image, Shape> getShapeMap() {
+		return shapeMap;
+	}
+
+	/**
+	 * @param shapeMap the shapeMap to set
+	 */
+	public void setShapeMap(Map<Image, Shape> shapeMap) {
+		this.shapeMap = shapeMap;
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/package.html b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/package.html
new file mode 100644
index 0000000..e14250b
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/decorators/package.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Mechanisms for associating data (shapes, colors, values, strings, etc.) with
+graph elements.
+
+</body>
+</html>
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/BoundingRectangleCollector.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/BoundingRectangleCollector.java
new file mode 100644
index 0000000..4df33dd
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/BoundingRectangleCollector.java
@@ -0,0 +1,86 @@
+package edu.uci.ics.jung.visualization.layout;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.List;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.visualization.RenderContext;
+
+public class BoundingRectangleCollector<V,E>  {
+
+	protected RenderContext<V,E> rc;
+	protected Graph<V,E> graph;
+	protected Layout<V,E> layout;
+	protected List<Rectangle2D> rectangles = new ArrayList<Rectangle2D>();
+	
+	public BoundingRectangleCollector(RenderContext<V, E> rc, Layout<V, E> layout) {
+		this.rc = rc;
+		this.layout = layout;
+		this.graph = layout.getGraph();
+		compute();
+	}
+
+	/**
+	 * @return the rectangles
+	 */
+	public List<Rectangle2D> getRectangles() {
+		return rectangles;
+	}
+
+	public void compute() {
+		rectangles.clear();
+//		Graphics2D g2d = (Graphics2D)g;
+//		g.setColor(Color.cyan);
+
+		for(E e : graph.getEdges()) {
+			Pair<V> endpoints = graph.getEndpoints(e);
+			V v1 = endpoints.getFirst();
+			V v2 = endpoints.getSecond();
+			Point2D p1 = layout.apply(v1);
+			Point2D p2 = layout.apply(v2);
+			float x1 = (float)p1.getX();
+			float y1 = (float)p1.getY();
+			float x2 = (float)p2.getX();
+			float y2 = (float)p2.getY();
+			
+			boolean isLoop = v1.equals(v2);
+			Shape s2 = rc.getVertexShapeTransformer().apply(v2);
+			Shape edgeShape = rc.getEdgeShapeTransformer().apply(e);
+
+			AffineTransform xform = AffineTransform.getTranslateInstance(x1,y1);
+			
+			if(isLoop) {
+				Rectangle2D s2Bounds = s2.getBounds2D();
+				xform.scale(s2Bounds.getWidth(), s2Bounds.getHeight());
+				xform.translate(0, -edgeShape.getBounds2D().getWidth()/2);
+			} else {
+				float dx = x2-x1;
+				float dy = y2-y1;
+				float theta = (float)Math.atan2(dy,dx);
+				xform.rotate(theta);
+				float dist = (float)p1.distance(p2);
+				xform.scale(dist, 1.0);
+			}
+			edgeShape = xform.createTransformedShape(edgeShape);
+//			edgeShape = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, edgeShape);
+			rectangles.add(edgeShape.getBounds2D());
+		}
+		
+		for(V v : graph.getVertices()) {
+			Shape shape = rc.getVertexShapeTransformer().apply(v);
+			Point2D p = layout.apply(v);
+//			p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p);
+			float x = (float)p.getX();
+			float y = (float)p.getY();
+			AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
+			shape = xform.createTransformedShape(shape);
+			rectangles.add(shape.getBounds2D());
+		}
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/BoundingRectanglePaintable.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/BoundingRectanglePaintable.java
new file mode 100644
index 0000000..06eb35f
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/BoundingRectanglePaintable.java
@@ -0,0 +1,56 @@
+package edu.uci.ics.jung.visualization.layout;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+import java.util.List;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+import edu.uci.ics.jung.visualization.util.ChangeEventSupport;
+
+public class BoundingRectanglePaintable<V,E> implements VisualizationServer.Paintable {
+
+	protected RenderContext<V,E> rc;
+	protected Graph<V,E> graph;
+	protected Layout<V,E> layout;
+	protected List<Rectangle2D> rectangles;
+	
+	public BoundingRectanglePaintable(RenderContext<V, E> rc, Layout<V, E> layout) {
+		super();
+		this.rc = rc;
+		this.layout = layout;
+		this.graph = layout.getGraph();
+		final BoundingRectangleCollector<V,E> brc = new BoundingRectangleCollector<V,E>(rc, layout);
+		this.rectangles = brc.getRectangles();
+		if(layout instanceof ChangeEventSupport) {
+			((ChangeEventSupport)layout).addChangeListener(new ChangeListener() {
+
+				public void stateChanged(ChangeEvent e) {
+					brc.compute();
+					rectangles = brc.getRectangles();
+				}});
+		}
+	}
+	
+	public void paint(Graphics g) {
+		Graphics2D g2d = (Graphics2D)g;
+		g.setColor(Color.cyan);
+		
+		for(Rectangle2D r : rectangles) {
+			g2d.draw(rc.getMultiLayerTransformer().transform(Layer.LAYOUT, r));
+		}
+	}
+
+	public boolean useTransform() {
+		return true;
+	}
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/CachingLayout.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/CachingLayout.java
new file mode 100644
index 0000000..52d8f7b
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/CachingLayout.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+
+package edu.uci.ics.jung.visualization.layout;
+
+import java.awt.geom.Point2D;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.LayoutDecorator;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.visualization.util.Caching;
+
+/**
+ * A LayoutDecorator that caches locations in a clearable Map. This can be used to ensure that
+ * edge endpoints are always the same as vertex locations when they are drawn in the render loop 
+ * during the time that the layout's relaxer thread is changing the locations.
+ * 
+ * @see LayoutDecorator
+ * @author Tom Nelson 
+ *
+ */
+public class CachingLayout<V, E> extends LayoutDecorator<V,E> implements Caching {
+    
+    protected LoadingCache<V, Point2D> locations;
+
+    public CachingLayout(Layout<V, E> delegate) {
+    	super(delegate);
+    	Function<V, Point2D> chain = Functions.<V,Point2D,Point2D>compose(
+    		new Function<Point2D,Point2D>() {
+    			public Point2D apply(Point2D p) {
+    				return (Point2D)p.clone();
+    		}}, 
+    		delegate);
+    	this.locations = CacheBuilder.newBuilder().build(CacheLoader.from(chain));
+    }
+    
+    @Override
+    public void setGraph(Graph<V, E> graph) {
+        delegate.setGraph(graph);
+    }
+
+	public void clear() {
+	    this.locations = CacheBuilder.newBuilder().build(new CacheLoader<V, Point2D>() {
+	    	public Point2D load(V vertex) {
+	    		return new Point2D.Double();
+	    	}
+	    });
+	}
+
+	public void init() {
+	}
+
+	public Point2D apply(V v) {
+		return locations.getUnchecked(v);
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/LayoutChangeListener.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/LayoutChangeListener.java
new file mode 100644
index 0000000..e4f50fb
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/LayoutChangeListener.java
@@ -0,0 +1,7 @@
+package edu.uci.ics.jung.visualization.layout;
+
+public interface LayoutChangeListener<V, E> {
+	
+	void layoutChanged(LayoutEvent<V,E> evt);
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/LayoutEvent.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/LayoutEvent.java
new file mode 100644
index 0000000..420b4a6
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/LayoutEvent.java
@@ -0,0 +1,26 @@
+package edu.uci.ics.jung.visualization.layout;
+
+import edu.uci.ics.jung.graph.Graph;
+
+public class LayoutEvent<V,E> {
+	
+	V vertex;
+	Graph<V,E> graph;
+
+	public LayoutEvent(V vertex, Graph<V, E> graph) {
+		this.vertex = vertex;
+		this.graph = graph;
+	}
+	public V getVertex() {
+		return vertex;
+	}
+	public void setVertex(V vertex) {
+		this.vertex = vertex;
+	}
+	public Graph<V, E> getGraph() {
+		return graph;
+	}
+	public void setGraph(Graph<V, E> graph) {
+		this.graph = graph;
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/LayoutEventSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/LayoutEventSupport.java
new file mode 100644
index 0000000..c28c440
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/LayoutEventSupport.java
@@ -0,0 +1,9 @@
+package edu.uci.ics.jung.visualization.layout;
+
+public interface LayoutEventSupport<V, E> {
+	
+	void addLayoutChangeListener(LayoutChangeListener<V,E> listener);
+	
+	void removeLayoutChangeListener(LayoutChangeListener<V,E> listener);
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/LayoutTransition.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/LayoutTransition.java
new file mode 100644
index 0000000..f058f5e
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/LayoutTransition.java
@@ -0,0 +1,57 @@
+package edu.uci.ics.jung.visualization.layout;
+
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.StaticLayout;
+import edu.uci.ics.jung.algorithms.layout.util.Relaxer;
+import edu.uci.ics.jung.algorithms.layout.util.VisRunner;
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+
+public class LayoutTransition<V,E> implements IterativeContext {
+	
+	protected Layout<V,E> startLayout;
+	protected Layout<V,E> endLayout;
+	protected Layout<V,E> transitionLayout;
+	protected boolean done = false;
+	protected int count = 20;
+	protected int counter = 0;
+	protected VisualizationViewer<V,E> vv;
+
+	public LayoutTransition(VisualizationViewer<V,E> vv, Layout<V, E> startLayout, Layout<V, E> endLayout) {
+		this.vv = vv;
+		this.startLayout = startLayout;
+		this.endLayout = endLayout;
+		if(endLayout instanceof IterativeContext) {
+			Relaxer relaxer = new VisRunner((IterativeContext)endLayout);
+			relaxer.prerelax();
+		}
+		this.transitionLayout =
+			new StaticLayout<V,E>(startLayout.getGraph(), startLayout);
+		vv.setGraphLayout(transitionLayout);
+	}
+
+	public boolean done() {
+		return done;
+	}
+
+	public void step() {
+		Graph<V,E> g = transitionLayout.getGraph();
+		for(V v : g.getVertices()) {
+			Point2D tp = transitionLayout.apply(v);
+			Point2D fp = endLayout.apply(v);
+			double dx = (fp.getX()-tp.getX())/(count-counter);
+			double dy = (fp.getY()-tp.getY())/(count-counter);
+			transitionLayout.setLocation(v, 
+					new Point2D.Double(tp.getX()+dx,tp.getY()+dy));
+		}
+		counter++;
+		if(counter >= count) {
+			done = true;
+			vv.setGraphLayout(endLayout);
+		}
+		vv.repaint();
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/ObservableCachingLayout.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/ObservableCachingLayout.java
new file mode 100644
index 0000000..39ee1f6
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/ObservableCachingLayout.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+
+package edu.uci.ics.jung.visualization.layout;
+
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.event.ChangeListener;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.LayoutDecorator;
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.visualization.util.Caching;
+import edu.uci.ics.jung.visualization.util.ChangeEventSupport;
+import edu.uci.ics.jung.visualization.util.DefaultChangeEventSupport;
+
+/**
+ * A LayoutDecorator that fires ChangeEvents when certain methods 
+ * are called. Used to wrap a Layout so that the visualization
+ * components can be notified of changes.
+ * 
+ * @see LayoutDecorator
+ * @author Tom Nelson 
+ *
+ */
+public class ObservableCachingLayout<V, E> extends LayoutDecorator<V,E> 
+	implements ChangeEventSupport, Caching, LayoutEventSupport<V,E> {
+    
+    protected ChangeEventSupport changeSupport = new DefaultChangeEventSupport(this);
+    
+    protected LoadingCache<V, Point2D> locations;
+    
+    private List<LayoutChangeListener<V,E>> layoutChangeListeners = 
+    	new ArrayList<LayoutChangeListener<V,E>>();
+
+    public ObservableCachingLayout(Layout<V, E> delegate) {
+    	super(delegate);
+		Function<V, Point2D> chain = Functions.<V, Point2D, Point2D> compose(
+				new Function<Point2D, Point2D>() {
+					public Point2D apply(Point2D p) {
+						return (Point2D) p.clone();
+					}
+				},
+				delegate);
+		this.locations = CacheBuilder.newBuilder().build(CacheLoader.from(chain));
+    }
+    
+    @Override
+    public void step() {
+    	super.step();
+    	fireStateChanged();
+    }
+
+	@Override
+    public void initialize() {
+		super.initialize();
+		fireStateChanged();
+	}
+	
+    @Override
+    public boolean done() {
+    	if(delegate instanceof IterativeContext) {
+    		return ((IterativeContext)delegate).done();
+    	}
+    	return true;
+    }
+
+
+	@Override
+    public void setLocation(V v, Point2D location) {
+		super.setLocation(v, location);
+		fireStateChanged();
+		fireLayoutChanged(v);
+	}
+
+    public void addChangeListener(ChangeListener l) {
+        changeSupport.addChangeListener(l);
+    }
+
+    public void removeChangeListener(ChangeListener l) {
+        changeSupport.removeChangeListener(l);
+    }
+
+    public ChangeListener[] getChangeListeners() {
+        return changeSupport.getChangeListeners();
+    }
+
+    public void fireStateChanged() {
+        changeSupport.fireStateChanged();
+    }
+    
+    @Override
+    public void setGraph(Graph<V, E> graph) {
+        delegate.setGraph(graph);
+    }
+
+	public void clear() {
+		this.locations.invalidateAll();
+	}
+
+	public void init() {
+	}
+
+	public Point2D apply(V v) {
+		return locations.getUnchecked(v);
+	}
+
+	private void fireLayoutChanged(V v) {
+		LayoutEvent<V,E> evt = new LayoutEvent<V,E>(v, this.getGraph());
+		for(LayoutChangeListener<V,E> listener : layoutChangeListeners) {
+			listener.layoutChanged(evt);
+		}
+	}
+	
+	public void addLayoutChangeListener(LayoutChangeListener<V, E> listener) {
+		layoutChangeListeners.add(listener);
+		
+	}
+
+	public void removeLayoutChangeListener(LayoutChangeListener<V, E> listener) {
+		layoutChangeListeners.remove(listener);
+		
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/PersistentLayout.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/PersistentLayout.java
new file mode 100644
index 0000000..4168257
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/PersistentLayout.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ * Created on Oct 9, 2004
+ *
+  */
+package edu.uci.ics.jung.visualization.layout;
+
+import java.awt.geom.Point2D;
+import java.io.IOException;
+import java.io.Serializable;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+
+/**
+ * interface for PersistentLayout
+ * Also holds a nested class Point to serialize the
+ * Vertex locations
+ * 
+ * @author Tom Nelson 
+ */
+public interface PersistentLayout<V, E> extends Layout<V,E> {
+    
+    void persist(String fileName) throws IOException;
+
+    void restore(String fileName) throws IOException, ClassNotFoundException;
+    
+    void lock(boolean state);
+    
+    /**
+     * a serializable class to save locations
+     */
+    @SuppressWarnings("serial")
+	static class Point implements Serializable {
+        public double x;
+        public double y;
+        public Point(double x, double y) {
+            this.x=x;
+            this.y=y;
+        }
+        public Point(Point2D p) {
+        	this.x = p.getX();
+        	this.y = p.getY();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/PersistentLayoutImpl.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/PersistentLayoutImpl.java
new file mode 100644
index 0000000..9d97499
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/PersistentLayoutImpl.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ * Created on Oct 8, 2004
+ *
+ */
+package edu.uci.ics.jung.visualization.layout;
+
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.visualization.util.Caching;
+import edu.uci.ics.jung.visualization.util.ChangeEventSupport;
+
+
+/**
+ * Implementation of PersistentLayout.
+ * Defers to another layout until 'restore' is called,
+ * then it uses the saved vertex locations
+ * 
+ * @author Tom Nelson
+ * 
+ *  
+ */
+public class PersistentLayoutImpl<V, E> extends ObservableCachingLayout<V,E>
+    implements PersistentLayout<V,E>,  ChangeEventSupport, Caching {
+
+    /**
+     * a container for Vertices
+     */
+    protected Map<V, Point> locations;
+    
+    /**
+     * a collection of Vertices that should not move
+     */
+    protected Set<V> dontmove;
+
+    /**
+     * whether the graph is locked (stops the VisualizationViewer rendering thread)
+     */
+    protected boolean locked;
+
+    /**
+     * create an instance with a passed layout
+     * create containers for graph components
+     * @param layout the layout whose positions are to be persisted
+     */
+    public PersistentLayoutImpl(Layout<V,E> layout) {
+        super(layout);
+	this.locations = Maps.asMap(
+	    ImmutableSet.copyOf(layout.getGraph().getVertices()),
+	    new RandomPointFactory<V>(getSize()));
+        this.dontmove = new HashSet<V>();
+    }
+
+    /**
+     * This method calls <tt>initialize_local_vertex</tt> for each vertex, and
+     * also adds initial coordinate information for each vertex. (The vertex's
+     * initial location is set by calling <tt>initializeLocation</tt>.
+     */
+    protected void initializeLocations() {
+        for(V v : getGraph().getVertices()) {
+            Point2D coord = delegate.apply(v);
+            if (!dontmove.contains(v))
+                initializeLocation(v, coord);
+        }
+    }
+
+
+    /**
+     * Sets persisted location for a vertex within the dimensions of the space.
+     * If the vertex has not been persisted, sets a random location. If you want
+     * to initialize in some different way, override this method.
+     * 
+     * @param v the vertex whose location is to be initialized
+     * @param coord the location 
+     */
+    protected void initializeLocation(V v, Point2D coord) {
+        Point point = locations.get(v);
+        coord.setLocation(point.x, point.y);
+    }
+
+    /**
+     * save the Vertex locations to a file
+     * @param fileName the file to save to	
+     * @throws IOException if the file cannot be used
+     */
+    public void persist(String fileName) throws IOException {
+
+        for(V v : getGraph().getVertices()) {
+            Point p = new Point(transform(v));
+            locations.put(v, p);
+        }
+        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
+                fileName));
+        oos.writeObject(locations);
+        oos.close();
+    }
+
+    /**
+     * Restore the graph Vertex locations from a file
+     * @param fileName the file to use
+     * @throws IOException for file problems
+     * @throws ClassNotFoundException for classpath problems
+     */
+    @SuppressWarnings("unchecked")
+	public void restore(String fileName) throws IOException,
+            ClassNotFoundException {
+        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
+                fileName));
+        locations = (Map<V, Point>) ois.readObject();
+        ois.close();
+        initializeLocations();
+        locked = true;
+        fireStateChanged();
+    }
+
+    public void lock(boolean locked) {
+        this.locked = locked;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see edu.uci.ics.jung.visualization.Layout#incrementsAreDone()
+     */
+    public boolean done() {
+        return super.done() || locked;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see edu.uci.ics.jung.visualization.Layout#lockVertex(edu.uci.ics.jung.graph.Vertex)
+     */
+    public void lock(V v, boolean state) {
+        dontmove.add(v);
+        delegate.lock(v, state);
+    }
+    
+    @SuppressWarnings("serial")
+	public static class RandomPointFactory<V> implements Function<V,Point>, Serializable {
+
+    	Dimension d;
+    	public RandomPointFactory(Dimension d) {
+    		this.d = d;
+    	}
+		public edu.uci.ics.jung.visualization.layout.PersistentLayout.Point apply(V v) {
+	            double x = Math.random() * d.width;
+	            double y = Math.random() * d.height;
+				return new Point(x,y);
+		}
+    }
+
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/package.html b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/package.html
new file mode 100644
index 0000000..b408741
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/layout/package.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Visualization mechanisms related to graph layout: caching, persistence, 
+event-emitting, etc.
+
+</body>
+</html>
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/package.html b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/package.html
new file mode 100644
index 0000000..d68801f
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Frameworks and mechanisms for visualizing JUNG graphs using Swing/AWT.
+
+</body>
+</html>
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/AbstractPickedState.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/AbstractPickedState.java
new file mode 100644
index 0000000..713c1c6
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/AbstractPickedState.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ *
+ * Created on Apr 2, 2005
+ */
+package edu.uci.ics.jung.visualization.picking;
+
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.event.EventListenerList;
+
+/**
+ * An abstract class to support ItemEvents for PickedState
+ * 
+ * @author Tom Nelson
+ */
+public abstract class AbstractPickedState<T> implements PickedState<T> {
+    
+    protected EventListenerList listenerList = new EventListenerList();
+
+    public void addItemListener(ItemListener l) {
+        listenerList.add(ItemListener.class, l);
+        
+    }
+
+    public void removeItemListener(ItemListener l) {
+        listenerList.remove(ItemListener.class, l);
+    }
+    
+    protected void fireItemStateChanged(ItemEvent e) {
+        Object[] listeners = listenerList.getListenerList();
+        for ( int i = listeners.length-2; i>=0; i-=2 ) {
+            if ( listeners[i]==ItemListener.class ) {
+                ((ItemListener)listeners[i+1]).itemStateChanged(e);
+            }
+        }
+    }   
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/ClosestShapePickSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/ClosestShapePickSupport.java
new file mode 100644
index 0000000..3d409e8
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/ClosestShapePickSupport.java
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2008, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Apr 24, 2008
+ *  
+ */
+package edu.uci.ics.jung.visualization.picking;
+
+import java.awt.Shape;
+import java.awt.geom.Point2D;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+
+/**
+ * A <code>GraphElementAccessor</code> that finds the closest element to 
+ * the pick point, and returns it if it is within the element's shape.
+ * This is best suited to elements with convex shapes that do not overlap.
+ * It differs from <code>ShapePickSupport</code> in that it only checks
+ * the closest element to see whether it contains the pick point.
+ * Possible unexpected odd behaviors:
+ * <ul>
+ * <li>If the elements overlap, this mechanism may pick another element than the one that's 
+ * "on top" (rendered last) if the pick point is closer to the center of an obscured vertex.
+ * <li>If element shapes are not convex, then this mechanism may return <code>null</code>
+ * even if the pick point is inside some element's shape, if the pick point is closer
+ * to the center of another element.
+ * </ul>
+ * Users who want to avoid either of these should use <code>ShapePickSupport</code>
+ * instead, which is slower but more flexible.  If neither of the above conditions
+ * (overlapping elements or non-convex shapes) is true, then <code>ShapePickSupport</code>
+ * and this class should have the same behavior.
+ */
+public class ClosestShapePickSupport<V,E> implements GraphElementAccessor<V,E> {
+	
+	protected VisualizationServer<V,E> vv;
+	protected float pickSize;
+
+	/**
+     * Creates a <code>ShapePickSupport</code> for the <code>vv</code>
+     * VisualizationServer, with the specified pick footprint.
+     * The <code>VisualizationServer</code> is used to fetch the current
+     * <code>Layout</code>. 
+     * @param vv source of the current <code>Layout</code>.
+     * @param pickSize the size of the pick footprint for line edges
+	 */
+	public ClosestShapePickSupport(VisualizationServer<V,E> vv, float pickSize)
+	{
+		this.vv = vv;
+		this.pickSize = pickSize;
+	}
+	
+	/**
+     * Create a <code>ShapePickSupport</code> with the <code>vv</code>
+     * VisualizationServer and default pick footprint.
+     * The footprint defaults to 2.
+     * @param vv source of the current <code>Layout</code>.
+	 */
+	public ClosestShapePickSupport(VisualizationServer<V,E> vv) 
+	{
+		this.vv = vv;
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.algorithms.layout.GraphElementAccessor#getEdge(edu.uci.ics.jung.algorithms.layout.Layout, double, double)
+	 */
+	public E getEdge(Layout<V,E> layout, double x, double y) 
+	{
+		return null;
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.algorithms.layout.GraphElementAccessor#getVertex(edu.uci.ics.jung.algorithms.layout.Layout, double, double)
+	 */
+	public V getVertex(Layout<V,E> layout, double x, double y) 
+	{
+		// first, find the closest vertex to (x,y)
+		double minDistance = Double.MAX_VALUE;
+        V closest = null;
+		while(true) 
+		{
+		    try 
+		    {
+                for(V v : layout.getGraph().getVertices()) 
+                {
+		            Point2D p = layout.apply(v);
+		            double dx = p.getX() - x;
+		            double dy = p.getY() - y;
+		            double dist = dx * dx + dy * dy;
+		            if (dist < minDistance) 
+		            {
+		                minDistance = dist;
+		                closest = v;
+		            }
+		        }
+		        break;
+		    } 
+		    catch(ConcurrentModificationException cme) {}
+		}
+		
+		// now check to see whether (x,y) is in the shape for this vertex.
+		
+		// get the vertex shape
+        Shape shape = vv.getRenderContext().getVertexShapeTransformer().apply(closest);
+        // get the vertex location
+        Point2D p = layout.apply(closest);
+        // transform the vertex location to screen coords
+        p = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p);
+        
+        double ox = x - p.getX();
+        double oy = y - p.getY();
+
+        if (shape.contains(ox, oy))
+        	return closest;
+        else
+        	return null;
+	}
+
+	/**
+	 * @see edu.uci.ics.jung.algorithms.layout.GraphElementAccessor#getVertices(edu.uci.ics.jung.algorithms.layout.Layout, java.awt.Shape)
+	 */
+	public Collection<V> getVertices(Layout<V,E> layout, Shape rectangle) 
+	{
+		// FIXME: RadiusPickSupport and ShapePickSupport are not using the same mechanism!
+		// talk to Tom and make sure I understand which should be used.
+		// in particular, there are some transformations that the latter uses; the latter is also 
+		// doing a couple of kinds of filtering.  (well, only one--just predicate-based.)
+		// looks to me like the VV could (should) be doing this filtering.  (maybe.)
+		// 
+		return null;
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/LayoutLensShapePickSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/LayoutLensShapePickSupport.java
new file mode 100644
index 0000000..1e6c482
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/LayoutLensShapePickSupport.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 11, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.picking;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.HashSet;
+import java.util.Set;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+
+/**
+ * ShapePickSupport provides access to Vertices and EdgeType based on
+ * their actual shapes. 
+ * 
+ * @author Tom Nelson
+ *
+ */
+public class LayoutLensShapePickSupport<V, E> extends ShapePickSupport<V,E> 
+	implements GraphElementAccessor<V,E> {
+
+    public LayoutLensShapePickSupport(VisualizationServer<V,E> vv, float pickSize) {
+    	super(vv,pickSize);
+    }
+    
+    public LayoutLensShapePickSupport(VisualizationServer<V,E> vv) {
+        this(vv,2);
+    }
+    
+    public V getVertex(Layout<V, E> layout, double x, double y) {
+
+        V closest = null;
+        double minDistance = Double.MAX_VALUE;
+
+        while(true) {
+            try {
+                for(V v : getFilteredVertices(layout)) {
+                	
+                    Shape shape = vv.getRenderContext().getVertexShapeTransformer().apply(v);
+                    // get the vertex location
+                    Point2D p = layout.apply(v);
+                    if(p == null) continue;
+                    // transform the vertex location to screen coords
+                    p = vv.getRenderContext().getMultiLayerTransformer().transform(p);
+                    AffineTransform xform = 
+                        AffineTransform.getTranslateInstance(p.getX(), p.getY());
+                    shape = xform.createTransformedShape(shape);
+                    
+                    // see if this vertex center is closest to the pick point
+                    // among any other containing vertices
+                    if(shape.contains(x, y)) {
+
+                    	if(style == Style.LOWEST) {
+                    		// return the first match
+                    		return v;
+                    	} else if(style == Style.HIGHEST) {
+                    		// will return the last match
+                    		closest = v;
+                    	} else {
+                    		Rectangle2D bounds = shape.getBounds2D();
+                    		double dx = bounds.getCenterX() - x;
+                    		double dy = bounds.getCenterY() - y;
+                    		double dist = dx * dx + dy * dy;
+                    		if (dist < minDistance) {
+                    			minDistance = dist;
+                    			closest = v;
+                    		}
+                    	}
+                    }
+                }
+                break;
+            } catch(ConcurrentModificationException cme) {}
+        }
+        return closest;
+    }
+
+    public Collection<V> getVertices(Layout<V, E> layout, Shape rectangle) {
+    	Set<V> pickedVertices = new HashSet<V>();
+    	
+        while(true) {
+            try {
+                for(V v : getFilteredVertices(layout)) {
+                    Point2D p = layout.apply(v);
+                    if(p == null) continue;
+
+                    p = vv.getRenderContext().getMultiLayerTransformer().transform(p);
+                    if(rectangle.contains(p)) {
+                    	pickedVertices.add(v);
+                    }
+                }
+                break;
+            } catch(ConcurrentModificationException cme) {}
+        }
+        return pickedVertices;
+    }
+
+    public E getEdge(Layout<V, E> layout, double x, double y) {
+
+        Point2D ip = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, new Point2D.Double(x,y));
+        x = ip.getX();
+        y = ip.getY();
+
+        // as a Line has no area, we can't always use edgeshape.contains(point) so we
+        // make a small rectangular pickArea around the point and check if the
+        // edgeshape.intersects(pickArea)
+        Rectangle2D pickArea = 
+            new Rectangle2D.Float((float)x-pickSize/2,(float)y-pickSize/2,pickSize,pickSize);
+        E closest = null;
+        double minDistance = Double.MAX_VALUE;
+        while(true) {
+            try {
+                for(E e : getFilteredEdges(layout)) {
+
+                    Pair<V> pair = layout.getGraph().getEndpoints(e);
+                    V v1 = pair.getFirst();
+                    V v2 = pair.getSecond();
+                    boolean isLoop = v1.equals(v2);
+                    Point2D p1 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, layout.apply(v1));
+                    Point2D p2 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, layout.apply(v2));
+                    if(p1 == null || p2 == null) continue;
+                    float x1 = (float) p1.getX();
+                    float y1 = (float) p1.getY();
+                    float x2 = (float) p2.getX();
+                    float y2 = (float) p2.getY();
+
+                    // translate the edge to the starting vertex
+                    AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
+
+                    Shape edgeShape = vv.getRenderContext().getEdgeShapeTransformer().apply(e);
+                    if(isLoop) {
+                        // make the loops proportional to the size of the vertex
+                        Shape s2 = vv.getRenderContext().getVertexShapeTransformer().apply(v2);
+                        Rectangle2D s2Bounds = s2.getBounds2D();
+                        xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight());
+                        // move the loop so that the nadir is centered in the vertex
+                        xform.translate(0, -edgeShape.getBounds2D().getHeight()/2);
+                    } else {
+                        float dx = x2 - x1;
+                        float dy = y2 - y1;
+                        // rotate the edge to the angle between the vertices
+                        double theta = Math.atan2(dy,dx);
+                        xform.rotate(theta);
+                        // stretch the edge to span the distance between the vertices
+                        float dist = (float) Math.sqrt(dx*dx + dy*dy);
+                        xform.scale(dist, 1.0f);
+                    }
+
+                    // transform the edge to its location and dimensions
+                    edgeShape = xform.createTransformedShape(edgeShape);
+
+                    // because of the transform, the edgeShape is now a GeneralPath
+                    // see if this edge is the closest of any that intersect
+                    if(edgeShape.intersects(pickArea)) {
+                        float cx=0;
+                        float cy=0;
+                        float[] f = new float[6];
+                        PathIterator pi = new GeneralPath(edgeShape).getPathIterator(null);
+                        if(pi.isDone()==false) {
+                            pi.next();
+                            pi.currentSegment(f);
+                            cx = f[0];
+                            cy = f[1];
+                            if(pi.isDone()==false) {
+                                pi.currentSegment(f);
+                                cx = f[0];
+                                cy = f[1];
+                            }
+                        }
+                        float dx = (float) (cx - x);
+                        float dy = (float) (cy - y);
+                        float dist = dx * dx + dy * dy;
+                        if (dist < minDistance) {
+                            minDistance = dist;
+                            closest = e;
+                        }
+                    }
+		        }
+		        break;
+		    } catch(ConcurrentModificationException cme) {}
+		}
+		return closest;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/MultiPickedState.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/MultiPickedState.java
new file mode 100644
index 0000000..865602c
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/MultiPickedState.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ *
+ * Created on Mar 28, 2005
+ */
+package edu.uci.ics.jung.visualization.picking;
+
+import java.awt.event.ItemEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Maintains the state of what has been 'picked' in the graph.
+ * The <code>Sets</code> are constructed so that their iterators
+ * will traverse them in the order in which they are picked.
+ * 
+ * @author Tom Nelson 
+ * @author Joshua O'Madadhain
+ * 
+ */
+public class MultiPickedState<T> extends AbstractPickedState<T> implements PickedState<T> {
+    /**
+     * the 'picked' vertices
+     */
+    protected Set<T> picked = new LinkedHashSet<T>();
+    
+    public boolean pick(T v, boolean state) {
+        boolean prior_state = this.picked.contains(v);
+        if (state) {
+            picked.add(v);
+            if(prior_state == false) {
+                fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED,
+                        v, ItemEvent.SELECTED));
+            }
+
+        } else {
+            picked.remove(v);
+            if(prior_state == true) {
+                fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED,
+                    v, ItemEvent.DESELECTED));
+            }
+
+        }
+        return prior_state;
+    }
+
+    public void clear() {
+        Collection<T> unpicks = new ArrayList<T>(picked);
+        for(T v : unpicks) {
+            pick(v, false);
+        }
+        picked.clear();
+
+    }
+
+    public Set<T> getPicked() {
+        return Collections.unmodifiableSet(picked);
+    }
+    
+    public boolean isPicked(T e) {
+        return picked.contains(e);
+    }
+
+    /**
+     * for the ItemSelectable interface contract
+     */
+    @SuppressWarnings("unchecked")
+    public T[] getSelectedObjects() {
+        List<T> list = new ArrayList<T>(picked);
+        return (T[])list.toArray();
+    }
+    
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/PickedInfo.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/PickedInfo.java
new file mode 100644
index 0000000..ce6ce2c
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/PickedInfo.java
@@ -0,0 +1,23 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.visualization.picking;
+
+
+
+/**
+ * An interface for classes that return information regarding whether a 
+ * given graph element (vertex or edge) has been selected.
+ * 
+ * @author danyelf
+ */
+public interface PickedInfo<T> {
+
+	public boolean isPicked(T t);
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/PickedState.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/PickedState.java
new file mode 100644
index 0000000..6e3a449
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/PickedState.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ *
+ * Created on Apr 2, 2005
+ */
+package edu.uci.ics.jung.visualization.picking;
+
+import java.awt.ItemSelectable;
+import java.util.Set;
+
+/**
+ * An interface for classes that keep track of the "picked" state
+ * of edges or vertices.
+ * 
+ * @author Tom Nelson
+ * @author Joshua O'Madadhain
+ */
+public interface PickedState<T> extends PickedInfo<T>, ItemSelectable {
+    /**
+     * Marks <code>v</code> as "picked" if <code>b == true</code>,
+     * and unmarks <code>v</code> as picked if <code>b == false</code>.
+     * @param v the element to be picked/unpicked
+     * @param b true if {@code v} is to be marked as picked, false if to be marked as unpicked
+     * @return the "picked" state of <code>v</code> prior to this call
+     */
+    boolean pick(T v, boolean b);
+    
+    /**
+     * Clears the "picked" state from all elements.
+     */
+    void clear();
+    
+    /**
+     * @return all "picked" elements.
+     */
+    Set<T> getPicked();
+    
+    /** 
+     * @return <code>true</code> if <code>v</code> is currently "picked".
+     */
+    boolean isPicked(T v);
+
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/RadiusPickSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/RadiusPickSupport.java
new file mode 100644
index 0000000..dc2151a
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/RadiusPickSupport.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+/*
+ * Created on Mar 19, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.picking;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.algorithms.layout.RadiusGraphElementAccessor;
+
+
+
+/**
+ * Simple implementation of PickSupport that returns the vertex or edge
+ * that is closest to the specified location.  This implementation
+ * provides the same picking options that were available in
+ * previous versions of AbstractLayout.
+ * 
+ * @author Tom Nelson
+ * @author Joshua O'Madadhain
+ */
+public class RadiusPickSupport<V, E> 
+    extends RadiusGraphElementAccessor<V, E> implements GraphElementAccessor<V,E> {
+    
+    public RadiusPickSupport() {
+        this(Math.sqrt(Double.MAX_VALUE - 1000));
+    }
+    
+    /**
+     * Creates an instance with the specified maximum distance.
+     * @param maxDistance the farthest that a vertex can be from the selection point
+     *     and still be 'picked'
+     */
+    public RadiusPickSupport(double maxDistance) {
+        super(maxDistance);
+    }
+    
+	/**
+	 * Gets the vertex nearest to the location of the (x,y) location selected,
+	 * within a distance of <tt>maxDistance</tt>. Iterates through all
+	 * visible vertices and checks their distance from the click. Override this
+	 * method to provide a more efficient implementation.
+	 */
+	public V getVertex(Layout<V,E> layout, double x, double y) {
+	    return getVertex(layout, x, y, this.maxDistance);
+	}
+
+	/**
+	 * Gets the vertex nearest to the location of the (x,y) location selected,
+	 * within a distance of <tt>maxDistance</tt>. Iterates through all
+	 * visible vertices and checks their distance from the click. Override this
+	 * method to provide a more efficient implementation.
+     * @param layout the layout instance that records the positions for all vertices
+     * @param x the x coordinate of the pick point
+     * @param y the y coordinate of the pick point
+	 * @param maxDistance vertices whose from (x, y) is > this cannot be returned
+     * @return the vertex whose center is closest to the pick point (x, y)
+	 */
+	public V getVertex(Layout<V,E> layout, double x, double y, double maxDistance) {
+	    return super.getVertex(layout, x, y, maxDistance);
+	}
+	
+	/**
+	 * Gets the edge nearest to the location of the (x,y) location selected.
+	 * Calls the longer form of the call.
+     * @param layout the layout instance that records the positions for all vertices
+     * @param x the x coordinate of the pick point
+     * @param y the y coordinate of the pick point
+     * @return the vertex whose center is closest to the pick point (x, y)
+	 */
+	public E getEdge(Layout<V,E> layout, double x, double y) {
+	    return getEdge(layout, x, y, this.maxDistance);
+	}
+
+	/**
+	 * Gets the edge nearest to the location of the (x,y) location selected,
+	 * within a distance of <tt>maxDistance</tt>, Iterates through all
+	 * visible edges and checks their distance from the click. Override this
+	 * method to provide a more efficient implementation.
+	 * 
+     * @param x the x coordinate of the pick point
+     * @param y the y coordinate of the pick point
+	 * @param maxDistance vertices whose from (x, y) is > this cannot be returned
+	 * @return Edge closest to the click.
+	 */
+	public E getEdge(Layout<V,E> layout, double x, double y, double maxDistance) {
+	    return super.getEdge(layout, x, y, maxDistance);
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/ShapePickSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/ShapePickSupport.java
new file mode 100644
index 0000000..4f8e0e8
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/ShapePickSupport.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 11, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.picking;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+
+/**
+ * A <code>GraphElementAccessor</code> that returns elements whose <code>Shape</code>
+ * contains the specified pick point or region.
+ * 
+ * @author Tom Nelson
+ *
+ */
+public class ShapePickSupport<V, E> implements GraphElementAccessor<V,E> {
+
+	/**
+	 * The available picking heuristics:
+     * <ul>
+     * <li><code>Style.CENTERED</code>: returns the element whose 
+     * center is closest to the pick point.
+     * <li><code>Style.LOWEST</code>: returns the first such element
+     * encountered.  (If the element collection has a consistent
+     * ordering, this will also be the element "on the bottom", 
+     * that is, the one which is rendered first.) 
+     * <li><code>Style.HIGHEST</code>: returns the last such element
+     * encountered.  (If the element collection has a consistent
+     * ordering, this will also be the element "on the top", 
+     * that is, the one which is rendered last.)
+     * </ul>
+	 *
+	 */
+	public static enum Style { LOWEST, CENTERED, HIGHEST };
+
+    protected float pickSize;
+    
+    /**
+     * The <code>VisualizationServer</code> in which the 
+     * this instance is being used for picking.  Used to 
+     * retrieve properties such as the layout, renderer,
+     * vertex and edge shapes, and coordinate transformations.
+     */
+    protected VisualizationServer<V,E> vv;
+    
+    /**
+     * The current picking heuristic for this instance.  Defaults
+     * to <code>CENTERED</code>.
+     */
+    protected Style style = Style.CENTERED;
+    
+    /**
+     * Creates a <code>ShapePickSupport</code> for the <code>vv</code>
+     * VisualizationServer, with the specified pick footprint and
+     * the default pick style.
+     * The <code>VisualizationServer</code> is used to access 
+     * properties of the current visualization (layout, renderer,
+     * coordinate transformations, vertex/edge shapes, etc.).
+     * @param vv source of the current <code>Layout</code>.
+     * @param pickSize the size of the pick footprint for line edges
+     */
+    public ShapePickSupport(VisualizationServer<V,E> vv, float pickSize) {
+    	this.vv = vv;
+        this.pickSize = pickSize;
+    }
+    
+    /**
+     * Create a <code>ShapePickSupport</code> for the specified
+     * <code>VisualizationServer</code> with a default pick footprint.
+     * of size 2.
+     * @param vv the visualization server used for rendering
+     */
+    public ShapePickSupport(VisualizationServer<V,E> vv) {
+        this.vv = vv;
+        this.pickSize = 2;
+    }
+    
+    /**
+     * Returns the style of picking used by this instance.
+     * This specifies which of the elements, among those
+     * whose shapes contain the pick point, is returned.
+     * The available styles are:
+     * <ul>
+     * <li><code>Style.CENTERED</code>: returns the element whose 
+     * center is closest to the pick point.
+     * <li><code>Style.LOWEST</code>: returns the first such element
+     * encountered.  (If the element collection has a consistent
+     * ordering, this will also be the element "on the bottom", 
+     * that is, the one which is rendered first.) 
+     * <li><code>Style.HIGHEST</code>: returns the last such element
+     * encountered.  (If the element collection has a consistent
+     * ordering, this will also be the element "on the top", 
+     * that is, the one which is rendered last.)
+     * </ul>
+     * 
+	 * @return the style of picking used by this instance
+	 */
+	public Style getStyle() {
+		return style;
+	}
+
+	/**
+	 * Specifies the style of picking to be used by this instance.
+     * This specifies which of the elements, among those
+     * whose shapes contain the pick point, will be returned.
+     * The available styles are:
+     * <ul>
+     * <li><code>Style.CENTERED</code>: returns the element whose 
+     * center is closest to the pick point.
+     * <li><code>Style.LOWEST</code>: returns the first such element
+     * encountered.  (If the element collection has a consistent
+     * ordering, this will also be the element "on the bottom", 
+     * that is, the one which is rendered first.) 
+     * <li><code>Style.HIGHEST</code>: returns the last such element
+     * encountered.  (If the element collection has a consistent
+     * ordering, this will also be the element "on the top", 
+     * that is, the one which is rendered last.)
+     * </ul>
+	 * @param style the style to set
+	 */
+	public void setStyle(Style style) {
+		this.style = style;
+	}
+
+	/** 
+     * Returns the vertex, if any, whose shape contains (x, y).
+     * If (x,y) is contained in more than one vertex's shape, returns
+     * the vertex whose center is closest to the pick point.
+     * 
+     * @param layout the layout instance that records the positions for all vertices
+     * @param x the x coordinate of the pick point
+     * @param y the y coordinate of the pick point
+     * @return the vertex whose shape contains (x,y), and whose center is closest to the pick point
+     */
+    public V getVertex(Layout<V, E> layout, double x, double y) {
+
+        V closest = null;
+        double minDistance = Double.MAX_VALUE;
+        Point2D ip = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, 
+        		new Point2D.Double(x,y));
+        x = ip.getX();
+        y = ip.getY();
+
+        while(true) {
+            try {
+                for(V v : getFilteredVertices(layout)) {
+                	
+                    Shape shape = vv.getRenderContext().getVertexShapeTransformer().apply(v);
+                    // get the vertex location
+                    Point2D p = layout.apply(v);
+                    if(p == null) continue;
+                    // transform the vertex location to screen coords
+                    p = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p);
+                    
+                    double ox = x - p.getX();
+                    double oy = y - p.getY();
+
+                    if(shape.contains(ox, oy)) {
+                    	
+                    	if(style == Style.LOWEST) {
+                    		// return the first match
+                    		return v;
+                    	} else if(style == Style.HIGHEST) {
+                    		// will return the last match
+                    		closest = v;
+                    	} else {
+                    		
+                    		// return the vertex closest to the
+                    		// center of a vertex shape
+	                        Rectangle2D bounds = shape.getBounds2D();
+	                        double dx = bounds.getCenterX() - ox;
+	                        double dy = bounds.getCenterY() - oy;
+	                        double dist = dx * dx + dy * dy;
+	                        if (dist < minDistance) {
+	                        	minDistance = dist;
+	                        	closest = v;
+	                        }
+                    	}
+                    }
+                }
+                break;
+            } catch(ConcurrentModificationException cme) {}
+        }
+        return closest;
+    }
+
+    /**
+     * Returns the vertices whose layout coordinates are contained in 
+     * <code>Shape</code>.
+     * The shape is in screen coordinates, and the graph vertices
+     * are transformed to screen coordinates before they are tested
+     * for inclusion.
+     * @return the <code>Collection</code> of vertices whose <code>layout</code>
+     * coordinates are contained in <code>shape</code>.
+     */
+    public Collection<V> getVertices(Layout<V, E> layout, Shape shape) {
+    	Set<V> pickedVertices = new HashSet<V>();
+    	
+    	// remove the view transform from the rectangle
+    	shape = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, shape);
+
+        while(true) {
+            try {
+                for(V v : getFilteredVertices(layout)) {
+                    Point2D p = layout.apply(v);
+                    if(p == null) continue;
+
+                    p = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p);
+                    if(shape.contains(p)) {
+                    	pickedVertices.add(v);
+                    }
+                }
+                break;
+            } catch(ConcurrentModificationException cme) {}
+        }
+        return pickedVertices;
+    }
+    
+    /**
+     * Returns an edge whose shape intersects the 'pickArea' footprint of the passed
+     * x,y, coordinates.
+     * 
+	 * @param layout the context in which the location is defined
+	 * @param x the x coordinate of the location
+	 * @param y the y coordinate of the location
+     * @return an edge whose shape intersects the pick area centered on the location {@code (x,y)}
+     */
+    public E getEdge(Layout<V, E> layout, double x, double y) {
+
+        Point2D ip = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, new Point2D.Double(x,y));
+        x = ip.getX();
+        y = ip.getY();
+
+        // as a Line has no area, we can't always use edgeshape.contains(point) so we
+        // make a small rectangular pickArea around the point and check if the
+        // edgeshape.intersects(pickArea)
+        Rectangle2D pickArea = 
+            new Rectangle2D.Float((float)x-pickSize/2,(float)y-pickSize/2,pickSize,pickSize);
+        E closest = null;
+        double minDistance = Double.MAX_VALUE;
+        while(true) {
+            try {
+                for(E e : getFilteredEdges(layout)) {
+
+                    Shape edgeShape = getTransformedEdgeShape(layout, e);
+                    if (edgeShape == null)
+                    	continue;
+
+                    // because of the transform, the edgeShape is now a GeneralPath
+                    // see if this edge is the closest of any that intersect
+                    if(edgeShape.intersects(pickArea)) {
+                        float cx=0;
+                        float cy=0;
+                        float[] f = new float[6];
+                        PathIterator pi = new GeneralPath(edgeShape).getPathIterator(null);
+                        if(pi.isDone()==false) {
+                            pi.next();
+                            pi.currentSegment(f);
+                            cx = f[0];
+                            cy = f[1];
+                            if(pi.isDone()==false) {
+                                pi.currentSegment(f);
+                                cx = f[0];
+                                cy = f[1];
+                            }
+                        }
+                        float dx = (float) (cx - x);
+                        float dy = (float) (cy - y);
+                        float dist = dx * dx + dy * dy;
+                        if (dist < minDistance) {
+                            minDistance = dist;
+                            closest = e;
+                        }
+                    }
+		        }
+		        break;
+		    } catch(ConcurrentModificationException cme) {}
+		}
+		return closest;
+    }
+
+    /**
+     * Retrieves the shape template for <code>e</code> and
+     * transforms it according to the positions of its endpoints
+     * in <code>layout</code>.
+     * @param layout the <code>Layout</code> which specifies
+     * <code>e</code>'s endpoints' positions
+     * @param e the edge whose shape is to be returned
+     * @return the transformed shape
+     */
+	private Shape getTransformedEdgeShape(Layout<V, E> layout, E e) {
+		Pair<V> pair = layout.getGraph().getEndpoints(e);
+		V v1 = pair.getFirst();
+		V v2 = pair.getSecond();
+		boolean isLoop = v1.equals(v2);
+		Point2D p1 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, layout.apply(v1));
+		Point2D p2 = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, layout.apply(v2));
+        if(p1 == null || p2 == null) 
+        	return null;
+		float x1 = (float) p1.getX();
+		float y1 = (float) p1.getY();
+		float x2 = (float) p2.getX();
+		float y2 = (float) p2.getY();
+
+		// translate the edge to the starting vertex
+		AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
+
+		Shape edgeShape = vv.getRenderContext().getEdgeShapeTransformer().apply(e);
+		if(isLoop) {
+		    // make the loops proportional to the size of the vertex
+		    Shape s2 = vv.getRenderContext().getVertexShapeTransformer().apply(v2);
+		    Rectangle2D s2Bounds = s2.getBounds2D();
+		    xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight());
+		    // move the loop so that the nadir is centered in the vertex
+		    xform.translate(0, -edgeShape.getBounds2D().getHeight()/2);
+		} else {
+		    float dx = x2 - x1;
+		    float dy = y2 - y1;
+		    // rotate the edge to the angle between the vertices
+		    double theta = Math.atan2(dy,dx);
+		    xform.rotate(theta);
+		    // stretch the edge to span the distance between the vertices
+		    float dist = (float) Math.sqrt(dx*dx + dy*dy);
+		    xform.scale(dist, 1.0f);
+		}
+
+		// transform the edge to its location and dimensions
+		edgeShape = xform.createTransformedShape(edgeShape);
+		return edgeShape;
+	}
+
+    protected Collection<V> getFilteredVertices(Layout<V,E> layout) {
+    	if(verticesAreFiltered()) {
+    		Collection<V> unfiltered = layout.getGraph().getVertices();
+    		Collection<V> filtered = new LinkedHashSet<V>();
+    		for(V v : unfiltered) {
+    			if(isVertexRendered(Context.<Graph<V,E>,V>getInstance(layout.getGraph(),v))) {
+    				filtered.add(v);
+    			}
+    		}
+    		return filtered;
+    	} else {
+    		return layout.getGraph().getVertices();
+    	}
+    }
+
+    protected Collection<E> getFilteredEdges(Layout<V,E> layout) {
+    	if(edgesAreFiltered()) {
+    		Collection<E> unfiltered = layout.getGraph().getEdges();
+    		Collection<E> filtered = new LinkedHashSet<E>();
+    		for(E e : unfiltered) {
+    			if(isEdgeRendered(Context.<Graph<V,E>,E>getInstance(layout.getGraph(),e))) {
+    				filtered.add(e);
+    			}
+    		}
+    		return filtered;
+    	} else {
+    		return layout.getGraph().getEdges();
+    	}
+    }
+    
+    /**
+     * Quick test to allow optimization of <code>getFilteredVertices()</code>.
+     * @return <code>true</code> if there is an active vertex filtering
+     * mechanism for this visualization, <code>false</code> otherwise
+     */
+    protected boolean verticesAreFiltered() {
+		Predicate<Context<Graph<V,E>,V>> vertexIncludePredicate =
+			vv.getRenderContext().getVertexIncludePredicate();
+		return vertexIncludePredicate != null &&
+			vertexIncludePredicate.equals(Predicates.alwaysTrue()) == false;
+    }
+    
+    /**
+     * Quick test to allow optimization of <code>getFilteredEdges()</code>.
+     * @return <code>true</code> if there is an active edge filtering
+     * mechanism for this visualization, <code>false</code> otherwise
+     */
+    protected boolean edgesAreFiltered() {
+		Predicate<Context<Graph<V,E>,E>> edgeIncludePredicate =
+			vv.getRenderContext().getEdgeIncludePredicate();
+		return edgeIncludePredicate != null &&
+			edgeIncludePredicate.equals(Predicates.alwaysTrue()) == false;
+    }
+    
+	/**
+	 * Returns <code>true</code> if this vertex in this graph is included 
+	 * in the collections of elements to be rendered, and <code>false</code> otherwise.
+	 * @param context the vertex and graph to be queried
+	 * @return <code>true</code> if this vertex is 
+	 * included in the collections of elements to be rendered, <code>false</code>
+	 * otherwise.
+	 */
+	protected boolean isVertexRendered(Context<Graph<V,E>,V> context) {
+		Predicate<Context<Graph<V,E>,V>> vertexIncludePredicate =
+			vv.getRenderContext().getVertexIncludePredicate();
+		return vertexIncludePredicate == null || vertexIncludePredicate.apply(context);
+	}
+	
+	/**
+	 * Returns <code>true</code> if this edge and its endpoints
+	 * in this graph are all included in the collections of
+	 * elements to be rendered, and <code>false</code> otherwise.
+	 * @param context the edge and graph to be queried
+	 * @return <code>true</code> if this edge and its endpoints are all
+	 * included in the collections of elements to be rendered, <code>false</code>
+	 * otherwise.
+	 */
+	protected boolean isEdgeRendered(Context<Graph<V,E>,E> context) {
+		Predicate<Context<Graph<V,E>,V>> vertexIncludePredicate =
+			vv.getRenderContext().getVertexIncludePredicate();
+		Predicate<Context<Graph<V,E>,E>> edgeIncludePredicate =
+			vv.getRenderContext().getEdgeIncludePredicate();
+		Graph<V,E> g = context.graph;
+		E e = context.element;
+		boolean edgeTest = edgeIncludePredicate == null || edgeIncludePredicate.apply(context);
+		Pair<V> endpoints = g.getEndpoints(e);
+		V v1 = endpoints.getFirst();
+		V v2 = endpoints.getSecond();
+		boolean endpointsTest = vertexIncludePredicate == null ||
+			(vertexIncludePredicate.apply(Context.<Graph<V,E>,V>getInstance(g,v1)) && 
+					vertexIncludePredicate.apply(Context.<Graph<V,E>,V>getInstance(g,v2)));
+		return edgeTest && endpointsTest;
+	}
+
+	/**
+	 * Returns the size of the edge picking area.
+	 * The picking area is square; the size is specified as the length of one
+	 * side, in view coordinates. 
+	 * @return the size of the edge picking area
+	 */
+	public float getPickSize() {
+		return pickSize;
+	}
+
+	/**
+	 * Sets the size of the edge picking area.
+	 * @param pickSize the length of one side of the (square) picking area, in view coordinates
+	 */
+	public void setPickSize(float pickSize) {
+		this.pickSize = pickSize;
+	}
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/ViewLensShapePickSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/ViewLensShapePickSupport.java
new file mode 100644
index 0000000..e0a9410
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/ViewLensShapePickSupport.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * Created on Mar 11, 2005
+ *
+ */
+package edu.uci.ics.jung.visualization.picking;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.HashSet;
+import java.util.Set;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformerDecorator;
+
+/**
+ * ShapePickSupport provides access to Vertices and EdgeType based on
+ * their actual shapes. 
+ * 
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ * 
+ * @author Tom Nelson
+ */
+public class ViewLensShapePickSupport<V, E> extends ShapePickSupport<V,E>
+	implements GraphElementAccessor<V,E> {
+
+    public ViewLensShapePickSupport(VisualizationServer<V,E> vv, float pickSize) {
+    	super(vv, pickSize);
+    }
+    
+    public ViewLensShapePickSupport(VisualizationServer<V,E> vv) {
+        this(vv, 2);
+    }
+
+    public V getVertex(Layout<V, E> layout, double x, double y) {
+
+        V closest = null;
+        double minDistance = Double.MAX_VALUE;
+        Point2D ip = ((MutableTransformerDecorator)vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)).getDelegate().inverseTransform(new Point2D.Double(x,y));
+        x = ip.getX();
+        y = ip.getY();
+
+        while(true) {
+            try {
+                for(V v : getFilteredVertices(layout)) {
+                	// get the shape
+                    Shape shape = vv.getRenderContext().getVertexShapeTransformer().apply(v);
+                    // transform the vertex location to screen coords
+                    Point2D p = layout.apply(v);
+                    if(p == null) continue;
+                    AffineTransform xform = 
+                        AffineTransform.getTranslateInstance(p.getX(), p.getY());
+                    shape = xform.createTransformedShape(shape);
+                    
+                    // use the LAYOUT transform to move the shape center without
+                    // modifying the actual shape
+                    Point2D lp = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.LAYOUT, p);
+                    AffineTransform xlate = AffineTransform.getTranslateInstance(
+                    		lp.getX()-p.getX(),lp.getY()-p.getY());
+                    shape = xlate.createTransformedShape(shape);
+                    // now use the VIEW transform to modify the actual shape
+                    
+                    shape = vv.getRenderContext().getMultiLayerTransformer().transform(Layer.VIEW, shape);
+                    	//vv.getRenderContext().getMultiLayerTransformer().transform(shape);
+                    
+                    // see if this vertex center is closest to the pick point
+                    // among any other containing vertices
+                    if(shape.contains(x, y)) {
+
+                    	if(style == Style.LOWEST) {
+                    		// return the first match
+                    		return v;
+                    	} else if(style == Style.HIGHEST) {
+                    		// will return the last match
+                    		closest = v;
+                    	} else {
+                    		Rectangle2D bounds = shape.getBounds2D();
+                    		double dx = bounds.getCenterX() - x;
+                    		double dy = bounds.getCenterY() - y;
+                    		double dist = dx * dx + dy * dy;
+                    		if (dist < minDistance) {
+                    			minDistance = dist;
+                    			closest = v;
+                    		}
+                    	}
+                    }
+                }
+                break;
+            } catch(ConcurrentModificationException cme) {}
+        }
+        return closest;
+    }
+
+    public Collection<V> getVertices(Layout<V, E> layout, Shape rectangle) {
+    	Set<V> pickedVertices = new HashSet<V>();
+    	
+//    	 remove the view transform from the rectangle
+    	rectangle = ((MutableTransformerDecorator)vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)).getDelegate().inverseTransform(rectangle);
+
+        while(true) {
+            try {
+                for(V v : getFilteredVertices(layout)) {
+                    Point2D p = layout.apply(v);
+                    if(p == null) continue;
+                   	// get the shape
+                    Shape shape = vv.getRenderContext().getVertexShapeTransformer().apply(v);
+
+                    AffineTransform xform = 
+                        AffineTransform.getTranslateInstance(p.getX(), p.getY());
+                    shape = xform.createTransformedShape(shape);
+                    
+                    shape = vv.getRenderContext().getMultiLayerTransformer().transform(shape);
+                    Rectangle2D bounds = shape.getBounds2D();
+                    p.setLocation(bounds.getCenterX(),bounds.getCenterY());
+
+                    if(rectangle.contains(p)) {
+                    	pickedVertices.add(v);
+                    }
+                }
+                break;
+            } catch(ConcurrentModificationException cme) {}
+        }
+        return pickedVertices;
+    }
+    
+    public E getEdge(Layout<V, E> layout, double x, double y) {
+
+        Point2D ip = ((MutableTransformerDecorator)vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW)).getDelegate().inverseTransform(new Point2D.Double(x,y));
+        x = ip.getX();
+        y = ip.getY();
+
+        // as a Line has no area, we can't always use edgeshape.contains(point) so we
+        // make a small rectangular pickArea around the point and check if the
+        // edgeshape.intersects(pickArea)
+        Rectangle2D pickArea = 
+            new Rectangle2D.Float((float)x-pickSize/2,(float)y-pickSize/2,pickSize,pickSize);
+        E closest = null;
+        double minDistance = Double.MAX_VALUE;
+        while(true) {
+            try {
+                for(E e : getFilteredEdges(layout)) {
+
+                    Pair<V> pair = layout.getGraph().getEndpoints(e);
+                    V v1 = pair.getFirst();
+                    V v2 = pair.getSecond();
+                    boolean isLoop = v1.equals(v2);
+                    Point2D p1 = layout.apply(v1);
+                    	//vv.getRenderContext().getBasicTransformer().transform(layout.transform(v1));
+                    Point2D p2 = layout.apply(v2);
+                    	//vv.getRenderContext().getBasicTransformer().transform(layout.transform(v2));
+                    if(p1 == null || p2 == null) continue;
+                    float x1 = (float) p1.getX();
+                    float y1 = (float) p1.getY();
+                    float x2 = (float) p2.getX();
+                    float y2 = (float) p2.getY();
+
+                    // translate the edge to the starting vertex
+                    AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
+
+                    Shape edgeShape = vv.getRenderContext().getEdgeShapeTransformer().apply(e);
+                    if(isLoop) {
+                        // make the loops proportional to the size of the vertex
+                        Shape s2 = vv.getRenderContext().getVertexShapeTransformer().apply(v2);
+                        Rectangle2D s2Bounds = s2.getBounds2D();
+                        xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight());
+                        // move the loop so that the nadir is centered in the vertex
+                        xform.translate(0, -edgeShape.getBounds2D().getHeight()/2);
+                    } else {
+                        float dx = x2 - x1;
+                        float dy = y2 - y1;
+                        // rotate the edge to the angle between the vertices
+                        double theta = Math.atan2(dy,dx);
+                        xform.rotate(theta);
+                        // stretch the edge to span the distance between the vertices
+                        float dist = (float) Math.sqrt(dx*dx + dy*dy);
+                        xform.scale(dist, 1.0f);
+                    }
+
+                    // transform the edge to its location and dimensions
+                    edgeShape = xform.createTransformedShape(edgeShape);
+                    
+                    edgeShape = vv.getRenderContext().getMultiLayerTransformer().transform(edgeShape);
+
+                    // because of the transform, the edgeShape is now a GeneralPath
+                    // see if this edge is the closest of any that intersect
+                    if(edgeShape.intersects(pickArea)) {
+                        float cx=0;
+                        float cy=0;
+                        float[] f = new float[6];
+                        PathIterator pi = new GeneralPath(edgeShape).getPathIterator(null);
+                        if(pi.isDone()==false) {
+                            pi.next();
+                            pi.currentSegment(f);
+                            cx = f[0];
+                            cy = f[1];
+                            if(pi.isDone()==false) {
+                                pi.currentSegment(f);
+                                cx = f[0];
+                                cy = f[1];
+                            }
+                        }
+                        float dx = (float) (cx - x);
+                        float dy = (float) (cy - y);
+                        float dist = dx * dx + dy * dy;
+                        if (dist < minDistance) {
+                            minDistance = dist;
+                            closest = e;
+                        }
+                    }
+		        }
+		        break;
+		    } catch(ConcurrentModificationException cme) {}
+		}
+		return closest;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/package.html b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/package.html
new file mode 100644
index 0000000..611bad6
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/picking/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Visualization mechanisms for supporting the selection of graph elements.
+
+</body>
+</html>
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicEdgeArrowRenderingSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicEdgeArrowRenderingSupport.java
new file mode 100644
index 0000000..4fb4ad9
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicEdgeArrowRenderingSupport.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.RenderContext;
+
+public class BasicEdgeArrowRenderingSupport<V,E> implements EdgeArrowRenderingSupport<V, E>  {
+
+    public AffineTransform getArrowTransform(RenderContext<V,E> rc, Shape edgeShape, Shape vertexShape) {
+    	GeneralPath path = new GeneralPath(edgeShape);
+        float[] seg = new float[6];
+        Point2D p1=null;
+        Point2D p2=null;
+        AffineTransform at = new AffineTransform();
+        // when the PathIterator is done, switch to the line-subdivide
+        // method to get the arrowhead closer.
+        for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) {
+            int ret = i.currentSegment(seg);
+            if(ret == PathIterator.SEG_MOVETO) {
+                p2 = new Point2D.Float(seg[0],seg[1]);
+            } else if(ret == PathIterator.SEG_LINETO) {
+                p1 = p2;
+                p2 = new Point2D.Float(seg[0],seg[1]);
+                if(vertexShape.contains(p2)) {
+                    at = getArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape);
+                    break;
+                }
+            } 
+        }
+        return at;
+    }
+
+    public AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, Shape edgeShape, Shape vertexShape) {
+        return getReverseArrowTransform(rc, edgeShape, vertexShape, true);
+    }
+            
+    public AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, Shape edgeShape, Shape vertexShape,
+            boolean passedGo) {
+    	GeneralPath path = new GeneralPath(edgeShape);
+        float[] seg = new float[6];
+        Point2D p1=null;
+        Point2D p2=null;
+
+        AffineTransform at = new AffineTransform();
+        for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) {
+            int ret = i.currentSegment(seg);
+            if(ret == PathIterator.SEG_MOVETO) {
+                p2 = new Point2D.Float(seg[0],seg[1]);
+            } else if(ret == PathIterator.SEG_LINETO) {
+                p1 = p2;
+                p2 = new Point2D.Float(seg[0],seg[1]);
+                if(passedGo == false && vertexShape.contains(p2)) {
+                    passedGo = true;
+                 } else if(passedGo==true &&
+                        vertexShape.contains(p2)==false) {
+                     at = getReverseArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape);
+                    break;
+                }
+            } 
+        }
+        return at;
+    }
+
+    public AffineTransform getArrowTransform(RenderContext<V,E> rc, Line2D edgeShape, Shape vertexShape) {
+        float dx = (float) (edgeShape.getX1()-edgeShape.getX2());
+        float dy = (float) (edgeShape.getY1()-edgeShape.getY2());
+        // iterate over the line until the edge shape will place the
+        // arrowhead closer than 'arrowGap' to the vertex shape boundary
+        while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) {
+            try {
+                edgeShape = getLastOutsideSegment(edgeShape, vertexShape);
+            } catch(IllegalArgumentException e) {
+                System.err.println(e.toString());
+                return null;
+            }
+            dx = (float) (edgeShape.getX1()-edgeShape.getX2());
+            dy = (float) (edgeShape.getY1()-edgeShape.getY2());
+        }
+        double atheta = Math.atan2(dx,dy)+Math.PI/2;
+        AffineTransform at = 
+            AffineTransform.getTranslateInstance(edgeShape.getX1(), edgeShape.getY1());
+        at.rotate(-atheta);
+        return at;
+    }
+
+    protected AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, Line2D edgeShape, Shape vertexShape) {
+        float dx = (float) (edgeShape.getX1()-edgeShape.getX2());
+        float dy = (float) (edgeShape.getY1()-edgeShape.getY2());
+        // iterate over the line until the edge shape will place the
+        // arrowhead closer than 'arrowGap' to the vertex shape boundary
+        while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) {
+            try {
+                edgeShape = getFirstOutsideSegment(edgeShape, vertexShape);
+            } catch(IllegalArgumentException e) {
+                System.err.println(e.toString());
+                return null;
+            }
+            dx = (float) (edgeShape.getX1()-edgeShape.getX2());
+            dy = (float) (edgeShape.getY1()-edgeShape.getY2());
+        }
+        // calculate the angle for the arrowhead
+        double atheta = Math.atan2(dx,dy)-Math.PI/2;
+        AffineTransform at = AffineTransform.getTranslateInstance(edgeShape.getX1(),edgeShape.getY1());
+        at.rotate(-atheta);
+        return at;
+    }
+    
+    /**
+     * Returns a line that intersects {@code shape}'s boundary.
+     * 
+     * @param line line to subdivide
+     * @param shape shape to compare with line
+     * @return a line that intersects the shape boundary
+     * @throws IllegalArgumentException if the passed line's point2 is not inside the shape
+     */
+    protected Line2D getLastOutsideSegment(Line2D line, Shape shape) {
+        if(shape.contains(line.getP2())==false) {
+            String errorString =
+                "line end point: "+line.getP2()+" is not contained in shape: "+shape.getBounds2D();
+            throw new IllegalArgumentException(errorString);
+            //return null;
+        }
+        Line2D left = new Line2D.Double();
+        Line2D right = new Line2D.Double();
+        // subdivide the line until its left segment intersects
+        // the shape boundary
+        do {
+            subdivide(line, left, right);
+            line = right;
+        } while(shape.contains(line.getP1())==false);
+        // now that right is completely inside shape,
+        // return left, which must be partially outside
+        return left;
+    }
+   
+    /**
+     * Returns a line that intersects {@code shape}'s boundary.
+     * 
+     * @param line line to subdivide
+     * @param shape shape to compare with line
+     * @return a line that intersects the shape boundary
+     * @throws IllegalArgumentException if the passed line's point1 is not inside the shape
+     */
+    protected Line2D getFirstOutsideSegment(Line2D line, Shape shape) {
+        
+        if(shape.contains(line.getP1())==false) {
+            String errorString = 
+                "line start point: "+line.getP1()+" is not contained in shape: "+shape.getBounds2D();
+            throw new IllegalArgumentException(errorString);
+        }
+        Line2D left = new Line2D.Float();
+        Line2D right = new Line2D.Float();
+        // subdivide the line until its right side intersects the
+        // shape boundary
+        do {
+            subdivide(line, left, right);
+            line = left;
+        } while(shape.contains(line.getP2())==false);
+        // now that left is completely inside shape,
+        // return right, which must be partially outside
+        return right;
+    }
+
+    /**
+     * divide a Line2D into 2 new Line2Ds that are returned
+     * in the passed left and right instances, if non-null
+     * @param src the line to divide
+     * @param left the left side, or null
+     * @param right the right side, or null
+     */
+    protected void subdivide(Line2D src,
+            Line2D left,
+            Line2D right) {
+        double x1 = src.getX1();
+        double y1 = src.getY1();
+        double x2 = src.getX2();
+        double y2 = src.getY2();
+        
+        double mx = x1 + (x2-x1)/2.0;
+        double my = y1 + (y2-y1)/2.0;
+        if (left != null) {
+            left.setLine(x1, y1, mx, my);
+        }
+        if (right != null) {
+            right.setLine(mx, my, x2, y2);
+        }
+    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicEdgeLabelRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicEdgeLabelRenderer.java
new file mode 100644
index 0000000..5c35b25
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicEdgeLabelRenderer.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
+
+public class BasicEdgeLabelRenderer<V,E> implements Renderer.EdgeLabel<V,E> {
+	
+	public Component prepareRenderer(RenderContext<V,E> rc, EdgeLabelRenderer graphLabelRenderer, Object value, 
+			boolean isSelected, E edge) {
+		return rc.getEdgeLabelRenderer().<E>getEdgeLabelRendererComponent(rc.getScreenDevice(), value, 
+				rc.getEdgeFontTransformer().apply(edge), isSelected, edge);
+	}
+    
+    public void labelEdge(RenderContext<V,E> rc, Layout<V,E> layout, E e, String label) {
+    	if(label == null || label.length() == 0) return;
+    	
+    	Graph<V,E> graph = layout.getGraph();
+        // don't draw edge if either incident vertex is not drawn
+        Pair<V> endpoints = graph.getEndpoints(e);
+        V v1 = endpoints.getFirst();
+        V v2 = endpoints.getSecond();
+        if (!rc.getEdgeIncludePredicate().apply(Context.<Graph<V,E>,E>getInstance(graph,e)))
+            return;
+
+        if (!rc.getVertexIncludePredicate().apply(Context.<Graph<V,E>,V>getInstance(graph,v1)) || 
+            !rc.getVertexIncludePredicate().apply(Context.<Graph<V,E>,V>getInstance(graph,v2)))
+            return;
+
+        Point2D p1 = layout.apply(v1);
+        Point2D p2 = layout.apply(v2);
+        p1 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p1);
+        p2 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p2);
+        float x1 = (float) p1.getX();
+        float y1 = (float) p1.getY();
+        float x2 = (float) p2.getX();
+        float y2 = (float) p2.getY();
+
+        GraphicsDecorator g = rc.getGraphicsContext();
+        float distX = x2 - x1;
+        float distY = y2 - y1;
+        double totalLength = Math.sqrt(distX * distX + distY * distY);
+
+        double closeness = rc.getEdgeLabelClosenessTransformer().apply(Context.<Graph<V,E>,E>getInstance(graph, e)).doubleValue();
+
+        int posX = (int) (x1 + (closeness) * distX);
+        int posY = (int) (y1 + (closeness) * distY);
+
+        int xDisplacement = (int) (rc.getLabelOffset() * (distY / totalLength));
+        int yDisplacement = (int) (rc.getLabelOffset() * (-distX / totalLength));
+        
+        Component component = prepareRenderer(rc, rc.getEdgeLabelRenderer(), label, 
+                rc.getPickedEdgeState().isPicked(e), e);
+        
+        Dimension d = component.getPreferredSize();
+
+        Shape edgeShape = rc.getEdgeShapeTransformer().apply(e);
+        
+        double parallelOffset = 1;
+
+        parallelOffset += rc.getParallelEdgeIndexFunction().getIndex(graph, e);
+
+        parallelOffset *= d.height;
+        if(edgeShape instanceof Ellipse2D) {
+            parallelOffset += edgeShape.getBounds().getHeight();
+            parallelOffset = -parallelOffset;
+        }
+        
+        
+        AffineTransform old = g.getTransform();
+        AffineTransform xform = new AffineTransform(old);
+        xform.translate(posX+xDisplacement, posY+yDisplacement);
+        double dx = x2 - x1;
+        double dy = y2 - y1;
+        if(rc.getEdgeLabelRenderer().isRotateEdgeLabels()) {
+            double theta = Math.atan2(dy, dx);
+            if(dx < 0) {
+                theta += Math.PI;
+            }
+            xform.rotate(theta);
+        }
+        if(dx < 0) {
+            parallelOffset = -parallelOffset;
+        }
+        
+        xform.translate(-d.width/2, -(d.height/2-parallelOffset));
+        g.setTransform(xform);
+        g.draw(component, rc.getRendererPane(), 0, 0, d.width, d.height, true);
+
+        g.setTransform(old);
+    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicEdgeRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicEdgeRenderer.java
new file mode 100644
index 0000000..703a9fc
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicEdgeRenderer.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Dimension;
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import javax.swing.JComponent;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.graph.util.EdgeIndexFunction;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.decorators.EdgeShape;
+import edu.uci.ics.jung.visualization.decorators.ParallelEdgeShapeTransformer;
+import edu.uci.ics.jung.visualization.transform.LensTransformer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
+
+public class BasicEdgeRenderer<V,E> implements Renderer.Edge<V,E> {
+	
+	protected EdgeArrowRenderingSupport<V,E> edgeArrowRenderingSupport =
+		new BasicEdgeArrowRenderingSupport<V,E>();
+
+    public void paintEdge(RenderContext<V,E> rc, Layout<V, E> layout, E e) {
+        GraphicsDecorator g2d = rc.getGraphicsContext();
+        Graph<V,E> graph = layout.getGraph();
+        if (!rc.getEdgeIncludePredicate().apply(Context.<Graph<V,E>,E>getInstance(graph,e)))
+            return;
+        
+        // don't draw edge if either incident vertex is not drawn
+        Pair<V> endpoints = graph.getEndpoints(e);
+        V v1 = endpoints.getFirst();
+        V v2 = endpoints.getSecond();
+        if (!rc.getVertexIncludePredicate().apply(Context.<Graph<V,E>,V>getInstance(graph,v1)) || 
+            !rc.getVertexIncludePredicate().apply(Context.<Graph<V,E>,V>getInstance(graph,v2)))
+            return;
+        
+        Stroke new_stroke = rc.getEdgeStrokeTransformer().apply(e);
+        Stroke old_stroke = g2d.getStroke();
+        if (new_stroke != null)
+            g2d.setStroke(new_stroke);
+        
+        drawSimpleEdge(rc, layout, e);
+
+        // restore paint and stroke
+        if (new_stroke != null)
+            g2d.setStroke(old_stroke);
+
+    }
+
+	protected Shape prepareFinalEdgeShape(RenderContext<V,E> rc, Layout<V, E> layout, E e, int[] coords, boolean[] loop) {
+        Graph<V,E> graph = layout.getGraph();
+        Pair<V> endpoints = graph.getEndpoints(e);
+        V v1 = endpoints.getFirst();
+        V v2 = endpoints.getSecond();
+        
+        Point2D p1 = layout.apply(v1);
+        Point2D p2 = layout.apply(v2);
+        p1 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p1);
+        p2 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p2);
+        float x1 = (float) p1.getX();
+        float y1 = (float) p1.getY();
+        float x2 = (float) p2.getX();
+        float y2 = (float) p2.getY();
+        coords[0] = (int)x1;
+        coords[1] = (int)y1;
+        coords[2] = (int)x2;
+        coords[3] = (int)y2;
+        
+        boolean isLoop = loop[0] = v1.equals(v2);
+        Shape s2 = rc.getVertexShapeTransformer().apply(v2);
+        Shape edgeShape = rc.getEdgeShapeTransformer().apply(e);
+        
+        AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
+        
+        if(isLoop) {
+            // this is a self-loop. scale it is larger than the vertex
+            // it decorates and translate it so that its nadir is
+            // at the center of the vertex.
+            Rectangle2D s2Bounds = s2.getBounds2D();
+            xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight());
+            xform.translate(0, -edgeShape.getBounds2D().getWidth()/2);
+        } else if(rc.getEdgeShapeTransformer() instanceof EdgeShape.Orthogonal) {
+            float dx = x2-x1;
+            float dy = y2-y1;
+            int index = 0;
+            if(rc.getEdgeShapeTransformer() instanceof ParallelEdgeShapeTransformer) {
+				@SuppressWarnings("unchecked")
+				EdgeIndexFunction<V,E> peif =
+					((ParallelEdgeShapeTransformer<V,E>)rc.getEdgeShapeTransformer())
+						.getEdgeIndexFunction();
+            	index = peif.getIndex(null, e);
+            	index *= 20;
+            }
+            GeneralPath gp = new GeneralPath();
+            gp.moveTo(0,0);// the xform will do the translation to x1,y1
+            if(x1 > x2) {
+            	if(y1 > y2) {
+            		gp.lineTo(0, index);
+            		gp.lineTo(dx-index, index);
+            		gp.lineTo(dx-index, dy);
+            		gp.lineTo(dx, dy);
+            	} else {
+            		gp.lineTo(0, -index);
+            		gp.lineTo(dx-index, -index);
+            		gp.lineTo(dx-index, dy);
+            		gp.lineTo(dx, dy);
+            	}
+
+            } else {
+            	if(y1 > y2) {
+            		gp.lineTo(0, index);
+            		gp.lineTo(dx+index, index);
+            		gp.lineTo(dx+index, dy);
+            		gp.lineTo(dx, dy);
+            		
+            	} else {
+            		gp.lineTo(0, -index);
+            		gp.lineTo(dx+index, -index);
+            		gp.lineTo(dx+index, dy);
+            		gp.lineTo(dx, dy);
+            		
+            	}
+            	
+            }
+
+            edgeShape = gp;
+        	
+        } else {
+            // this is a normal edge. Rotate it to the angle between
+            // vertex endpoints, then scale it to the distance between
+            // the vertices
+            float dx = x2-x1;
+            float dy = y2-y1;
+            float thetaRadians = (float) Math.atan2(dy, dx);
+            xform.rotate(thetaRadians);
+            float dist = (float) Math.sqrt(dx*dx + dy*dy);
+            xform.scale(dist, 1.0);
+        }
+        
+        edgeShape = xform.createTransformedShape(edgeShape);
+        
+        return edgeShape;
+	}
+
+	/**
+     * Draws the edge <code>e</code>, whose endpoints are at <code>(x1,y1)</code>
+     * and <code>(x2,y2)</code>, on the graphics context <code>g</code>.
+     * The <code>Shape</code> provided by the <code>EdgeShapeFunction</code> instance
+     * is scaled in the x-direction so that its width is equal to the distance between
+     * <code>(x1,y1)</code> and <code>(x2,y2)</code>.
+     * @param rc the render context used for rendering the edge
+     * @param layout the layout instance which provides the edge's endpoints' coordinates
+     * @param e the edge to be drawn
+     */
+    protected void drawSimpleEdge(RenderContext<V,E> rc, Layout<V,E> layout, E e) {
+    	
+    	int[] coords = new int[4];
+    	boolean[] loop = new boolean[1];
+    	Shape edgeShape = prepareFinalEdgeShape(rc, layout, e, coords, loop);
+    	
+    	int x1 = coords[0];
+    	int y1 = coords[1];
+    	int x2 = coords[2];
+    	int y2 = coords[3];
+    	boolean isLoop = loop[0];
+        
+        GraphicsDecorator g = rc.getGraphicsContext();
+        Graph<V,E> graph = layout.getGraph();
+        boolean edgeHit = true;
+        boolean arrowHit = true;
+        Rectangle deviceRectangle = null;
+        JComponent vv = rc.getScreenDevice();
+        if(vv != null) {
+            Dimension d = vv.getSize();
+            deviceRectangle = new Rectangle(0,0,d.width,d.height);
+        }
+        
+        MutableTransformer vt = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW);
+        if(vt instanceof LensTransformer) {
+        	vt = ((LensTransformer)vt).getDelegate();
+        }
+        edgeHit = vt.transform(edgeShape).intersects(deviceRectangle);
+
+        if(edgeHit == true) {
+            
+            Paint oldPaint = g.getPaint();
+            
+            // get Paints for filling and drawing
+            // (filling is done first so that drawing and label use same Paint)
+            Paint fill_paint = rc.getEdgeFillPaintTransformer().apply(e); 
+            if (fill_paint != null)
+            {
+                g.setPaint(fill_paint);
+                g.fill(edgeShape);
+            }
+            Paint draw_paint = rc.getEdgeDrawPaintTransformer().apply(e);
+            if (draw_paint != null)
+            {
+                g.setPaint(draw_paint);
+                g.draw(edgeShape);
+            }
+            
+            float scalex = (float)g.getTransform().getScaleX();
+            float scaley = (float)g.getTransform().getScaleY();
+            // see if arrows are too small to bother drawing
+            if(scalex < .3 || scaley < .3) return;
+            
+            if (rc.getEdgeArrowPredicate().apply(Context.<Graph<V,E>,E>getInstance(graph, e))) {
+            	
+                Stroke new_stroke = rc.getEdgeArrowStrokeTransformer().apply(e);
+                Stroke old_stroke = g.getStroke();
+                if (new_stroke != null)
+                    g.setStroke(new_stroke);
+
+                
+                Shape destVertexShape = 
+                    rc.getVertexShapeTransformer().apply(graph.getEndpoints(e).getSecond());
+
+                AffineTransform xf = AffineTransform.getTranslateInstance(x2, y2);
+                destVertexShape = xf.createTransformedShape(destVertexShape);
+                
+                arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(destVertexShape).intersects(deviceRectangle);
+                if(arrowHit) {
+                    
+                    AffineTransform at = 
+                        edgeArrowRenderingSupport.getArrowTransform(rc, edgeShape, destVertexShape);
+                    if(at == null) return;
+                    Shape arrow = rc.getEdgeArrowTransformer().apply(Context.<Graph<V,E>,E>getInstance(graph, e));
+                    arrow = at.createTransformedShape(arrow);
+                    g.setPaint(rc.getArrowFillPaintTransformer().apply(e));
+                    g.fill(arrow);
+                    g.setPaint(rc.getArrowDrawPaintTransformer().apply(e));
+                    g.draw(arrow);
+                }
+                if (graph.getEdgeType(e) == EdgeType.UNDIRECTED) {
+                    Shape vertexShape = 
+                        rc.getVertexShapeTransformer().apply(graph.getEndpoints(e).getFirst());
+                    xf = AffineTransform.getTranslateInstance(x1, y1);
+                    vertexShape = xf.createTransformedShape(vertexShape);
+                    
+                    arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(vertexShape).intersects(deviceRectangle);
+                    
+                    if(arrowHit) {
+                        AffineTransform at = edgeArrowRenderingSupport.getReverseArrowTransform(rc, edgeShape, vertexShape, !isLoop);
+                        if(at == null) return;
+                        Shape arrow = rc.getEdgeArrowTransformer().apply(Context.<Graph<V,E>,E>getInstance(graph, e));
+                        arrow = at.createTransformedShape(arrow);
+                        g.setPaint(rc.getArrowFillPaintTransformer().apply(e));
+                        g.fill(arrow);
+                        g.setPaint(rc.getArrowDrawPaintTransformer().apply(e));
+                        g.draw(arrow);
+                    }
+                }
+                // restore paint and stroke
+                if (new_stroke != null)
+                    g.setStroke(old_stroke);
+
+            }
+            
+            // restore old paint
+            g.setPaint(oldPaint);
+        }
+    }
+
+	public EdgeArrowRenderingSupport<V, E> getEdgeArrowRenderingSupport() {
+		return edgeArrowRenderingSupport;
+	}
+
+	public void setEdgeArrowRenderingSupport(
+			EdgeArrowRenderingSupport<V, E> edgeArrowRenderingSupport) {
+		this.edgeArrowRenderingSupport = edgeArrowRenderingSupport;
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicRenderer.java
new file mode 100644
index 0000000..c5d4e6b
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicRenderer.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.util.ConcurrentModificationException;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.visualization.RenderContext;
+
+/**
+ * The default implementation of the Renderer used by the
+ * VisualizationViewer. Default Vertex and Edge Renderers
+ * are supplied, or the user may set custom values. The
+ * Vertex and Edge renderers are used in the renderVertex
+ * and renderEdge methods, which are called in the render
+ * loop of the VisualizationViewer.
+ * 
+ * @author Tom Nelson
+ */
+public class BasicRenderer<V,E> implements Renderer<V, E> {
+	
+    Renderer.Vertex<V,E> vertexRenderer = new BasicVertexRenderer<V,E>();
+    Renderer.VertexLabel<V,E> vertexLabelRenderer = new BasicVertexLabelRenderer<V,E>();
+    Renderer.Edge<V,E> edgeRenderer = new BasicEdgeRenderer<V,E>();
+    Renderer.EdgeLabel<V,E> edgeLabelRenderer = new BasicEdgeLabelRenderer<V,E>();
+    
+	public void render(RenderContext<V, E> renderContext, Layout<V, E> layout) {
+		
+		// paint all the edges
+        try {
+        	for(E e : layout.getGraph().getEdges()) {
+
+		        renderEdge(
+		                renderContext,
+		                layout,
+		                e);
+		        renderEdgeLabel(
+		                renderContext,
+		                layout,
+		                e);
+        	}
+        } catch(ConcurrentModificationException cme) {
+        	renderContext.getScreenDevice().repaint();
+        }
+		
+		// paint all the vertices
+        try {
+        	for(V v : layout.getGraph().getVertices()) {
+
+		    	renderVertex(
+		                renderContext,
+                        layout,
+		                v);
+		    	renderVertexLabel(
+		                renderContext,
+                        layout,
+		                v);
+        	}
+        } catch(ConcurrentModificationException cme) {
+            renderContext.getScreenDevice().repaint();
+        }
+	}
+
+    public void renderVertex(RenderContext<V,E> rc, Layout<V,E> layout, V v) {
+        vertexRenderer.paintVertex(rc, layout, v);
+    }
+    
+    public void renderVertexLabel(RenderContext<V,E> rc, Layout<V,E> layout, V v) {
+        vertexLabelRenderer.labelVertex(rc, layout, v, rc.getVertexLabelTransformer().apply(v));
+    }
+    
+    public void renderEdge(RenderContext<V,E> rc, Layout<V,E> layout, E e) {
+    	edgeRenderer.paintEdge(rc, layout, e);
+    }
+    
+    public void renderEdgeLabel(RenderContext<V,E> rc, Layout<V,E> layout, E e) {
+    	edgeLabelRenderer.labelEdge(rc, layout, e, rc.getEdgeLabelTransformer().apply(e));
+    }
+    
+    public void setVertexRenderer(Renderer.Vertex<V,E> r) {
+    	this.vertexRenderer = r;
+    }
+
+    public void setEdgeRenderer(Renderer.Edge<V,E> r) {
+    	this.edgeRenderer = r;
+    }
+
+	/**
+	 * @return the edgeLabelRenderer
+	 */
+	public Renderer.EdgeLabel<V, E> getEdgeLabelRenderer() {
+		return edgeLabelRenderer;
+	}
+
+	/**
+	 * @param edgeLabelRenderer the edgeLabelRenderer to set
+	 */
+	public void setEdgeLabelRenderer(Renderer.EdgeLabel<V, E> edgeLabelRenderer) {
+		this.edgeLabelRenderer = edgeLabelRenderer;
+	}
+
+	/**
+	 * @return the vertexLabelRenderer
+	 */
+	public Renderer.VertexLabel<V, E> getVertexLabelRenderer() {
+		return vertexLabelRenderer;
+	}
+
+	/**
+	 * @param vertexLabelRenderer the vertexLabelRenderer to set
+	 */
+	public void setVertexLabelRenderer(
+			Renderer.VertexLabel<V, E> vertexLabelRenderer) {
+		this.vertexLabelRenderer = vertexLabelRenderer;
+	}
+
+	/**
+	 * @return the edgeRenderer
+	 */
+	public Renderer.Edge<V, E> getEdgeRenderer() {
+		return edgeRenderer;
+	}
+
+	/**
+	 * @return the vertexRenderer
+	 */
+	public Renderer.Vertex<V, E> getVertexRenderer() {
+		return vertexRenderer;
+	}
+
+
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicVertexLabelRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicVertexLabelRenderer.java
new file mode 100644
index 0000000..8c8e31b
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicVertexLabelRenderer.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+package edu.uci.ics.jung.visualization.renderers;
+
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
+import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.TransformingGraphics;
+
+public class BasicVertexLabelRenderer<V,E> implements Renderer.VertexLabel<V,E> {
+
+	protected Position position = Position.SE;
+	private Positioner positioner = new OutsidePositioner();
+	
+	public BasicVertexLabelRenderer() {
+		super();
+	}
+
+	public BasicVertexLabelRenderer(Position position) {
+		this.position = position;
+	}
+
+	/**
+	 * @return the position
+	 */
+	public Position getPosition() {
+		return position;
+	}
+
+	/**
+	 * @param position the position to set
+	 */
+	public void setPosition(Position position) {
+		this.position = position;
+	}
+
+	public Component prepareRenderer(RenderContext<V,E> rc, VertexLabelRenderer graphLabelRenderer, Object value, 
+			boolean isSelected, V vertex) {
+		return rc.getVertexLabelRenderer().<V>getVertexLabelRendererComponent(rc.getScreenDevice(), value, 
+				rc.getVertexFontTransformer().apply(vertex), isSelected, vertex);
+	}
+
+	/**
+	 * Labels the specified vertex with the specified label.  
+	 * Uses the font specified by this instance's 
+	 * <code>VertexFontFunction</code>.  (If the font is unspecified, the existing
+	 * font for the graphics context is used.)  If vertex label centering
+	 * is active, the label is centered on the position of the vertex; otherwise
+     * the label is offset slightly.
+     */
+    public void labelVertex(RenderContext<V,E> rc, Layout<V,E> layout, V v, String label) {
+    	Graph<V,E> graph = layout.getGraph();
+        if (rc.getVertexIncludePredicate().apply(Context.<Graph<V,E>,V>getInstance(graph,v)) == false) {
+        	return;
+        }
+        Point2D pt = layout.apply(v);
+        pt = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, pt);
+
+        float x = (float) pt.getX();
+        float y = (float) pt.getY();
+
+        Component component = prepareRenderer(rc, rc.getVertexLabelRenderer(), label,
+        		rc.getPickedVertexState().isPicked(v), v);
+        GraphicsDecorator g = rc.getGraphicsContext();
+        Dimension d = component.getPreferredSize();
+        AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
+        
+    	Shape shape = rc.getVertexShapeTransformer().apply(v);
+    	shape = xform.createTransformedShape(shape);
+    	if(rc.getGraphicsContext() instanceof TransformingGraphics) {
+    		BidirectionalTransformer transformer = ((TransformingGraphics)rc.getGraphicsContext()).getTransformer();
+    		if(transformer instanceof ShapeTransformer) {
+    			ShapeTransformer shapeTransformer = (ShapeTransformer)transformer;
+    			shape = shapeTransformer.transform(shape);
+    		}
+    	}
+    	Rectangle2D bounds = shape.getBounds2D();
+
+    	Point p = null;
+    	if(position == Position.AUTO) {
+    		Dimension vvd = rc.getScreenDevice().getSize();
+    		if(vvd.width == 0 || vvd.height == 0) {
+    			vvd = rc.getScreenDevice().getPreferredSize();
+    		}
+    		p = getAnchorPoint(bounds, d, positioner.getPosition(x, y, vvd));
+    	} else {
+    		p = getAnchorPoint(bounds, d, position);
+    	}
+        g.draw(component, rc.getRendererPane(), p.x, p.y, d.width, d.height, true);
+    }
+    
+    protected Point getAnchorPoint(Rectangle2D vertexBounds, Dimension labelSize, Position position) {
+    	double x;
+    	double y;
+    	int offset = 5;
+    	switch(position) {
+    	
+    	case N:
+    		x = vertexBounds.getCenterX()-labelSize.width/2;
+    		y = vertexBounds.getMinY()-offset - labelSize.height;
+    		return new Point((int)x,(int)y);
+
+    	case NE:
+    		x = vertexBounds.getMaxX()+offset;
+    		y = vertexBounds.getMinY()-offset-labelSize.height;
+    		return new Point((int)x,(int)y);
+
+    	case E:
+    		x = vertexBounds.getMaxX()+offset;
+    		y = vertexBounds.getCenterY()-labelSize.height/2;
+    		return new Point((int)x,(int)y);
+
+    	case SE:
+    		x = vertexBounds.getMaxX()+offset;
+    		y = vertexBounds.getMaxY()+offset;
+    		return new Point((int)x,(int)y);
+
+    	case S:
+    		x = vertexBounds.getCenterX()-labelSize.width/2;
+    		y = vertexBounds.getMaxY()+offset;
+    		return new Point((int)x,(int)y);
+
+    	case SW:
+    		x = vertexBounds.getMinX()-offset-labelSize.width;
+    		y = vertexBounds.getMaxY()+offset;
+    		return new Point((int)x,(int)y);
+
+    	case W:
+    		x = vertexBounds.getMinX()-offset-labelSize.width;
+    		y = vertexBounds.getCenterY()-labelSize.height/2;
+    		return new Point((int)x,(int)y);
+
+    	case NW:
+    		x = vertexBounds.getMinX()-offset-labelSize.width;
+    		y = vertexBounds.getMinY()-offset-labelSize.height;
+    		return new Point((int)x,(int)y);
+
+    	case CNTR:
+    		x = vertexBounds.getCenterX()-labelSize.width/2;
+    		y = vertexBounds.getCenterY()-labelSize.height/2;
+    		return new Point((int)x,(int)y);
+
+    	default:
+    		return new Point();
+    	}
+    	
+    }
+    public static class InsidePositioner implements Positioner {
+    	public Position getPosition(float x, float y, Dimension d) {
+    		int cx = d.width/2;
+    		int cy = d.height/2;
+    		if(x > cx && y > cy) return Position.NW;
+    		if(x > cx && y < cy) return Position.SW;
+    		if(x < cx && y > cy) return Position.NE;
+    		return Position.SE;
+    	}
+    }
+    public static class OutsidePositioner implements Positioner {
+    	public Position getPosition(float x, float y, Dimension d) {
+    		int cx = d.width/2;
+    		int cy = d.height/2;
+    		if(x > cx && y > cy) return Position.SE;
+    		if(x > cx && y < cy) return Position.NE;
+    		if(x < cx && y > cy) return Position.SW;
+    		return Position.NW;
+    	}
+    }
+	/**
+	 * @return the positioner
+	 */
+	public Positioner getPositioner() {
+		return positioner;
+	}
+
+	/**
+	 * @param positioner the positioner to set
+	 */
+	public void setPositioner(Positioner positioner) {
+		this.positioner = positioner;
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicVertexRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicVertexRenderer.java
new file mode 100644
index 0000000..f4e49f1
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/BasicVertexRenderer.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Dimension;
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformerDecorator;
+import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
+
+public class BasicVertexRenderer<V,E> implements Renderer.Vertex<V,E> {
+
+    public void paintVertex(RenderContext<V,E> rc, Layout<V,E> layout, V v) {
+    	Graph<V,E> graph = layout.getGraph();
+        if (rc.getVertexIncludePredicate().apply(Context.<Graph<V,E>,V>getInstance(graph,v))) {
+        	paintIconForVertex(rc, v, layout);
+        }
+    }
+    
+    /**
+     * Returns the vertex shape in view coordinates.
+     * @param rc the render context used for rendering the vertex
+     * @param v the vertex whose shape is to be returned
+     * @param layout the layout algorithm that provides coordinates for the vertex
+     * @param coords the x and y view coordinates
+     * @return the vertex shape in view coordinates
+     */
+    protected Shape prepareFinalVertexShape(RenderContext<V,E> rc, V v, 
+    		Layout<V,E> layout, int[] coords) {
+
+        // get the shape to be rendered
+        Shape shape = rc.getVertexShapeTransformer().apply(v);
+        Point2D p = layout.apply(v);
+        p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p);
+        float x = (float)p.getX();
+        float y = (float)p.getY();
+        coords[0] = (int)x;
+        coords[1] = (int)y;
+        // create a transform that translates to the location of
+        // the vertex to be rendered
+        AffineTransform xform = AffineTransform.getTranslateInstance(x,y);
+        // transform the vertex shape with xtransform
+        shape = xform.createTransformedShape(shape);
+        return shape;
+    }
+    
+    /**
+     * Paint <code>v</code>'s icon on <code>g</code> at <code>(x,y)</code>.
+     * 
+     * @param rc the render context used for rendering the vertex
+     * @param v the vertex to be painted
+     * @param layout the layout algorithm that provides coordinates for the vertex
+     */
+    protected void paintIconForVertex(RenderContext<V,E> rc, V v, Layout<V,E> layout) {
+        GraphicsDecorator g = rc.getGraphicsContext();
+        boolean vertexHit = true;
+        int[] coords = new int[2];
+        Shape shape = prepareFinalVertexShape(rc, v, layout, coords);
+        vertexHit = vertexHit(rc, shape);
+
+        if (vertexHit) {
+        	if(rc.getVertexIconTransformer() != null) {
+        		Icon icon = rc.getVertexIconTransformer().apply(v);
+        		if(icon != null) {
+        		
+           			g.draw(icon, rc.getScreenDevice(), shape, coords[0], coords[1]);
+
+        		} else {
+        			paintShapeForVertex(rc, v, shape);
+        		}
+        	} else {
+        		paintShapeForVertex(rc, v, shape);
+        	}
+        }
+    }
+    
+    protected boolean vertexHit(RenderContext<V,E> rc, Shape s) {
+        JComponent vv = rc.getScreenDevice();
+        Rectangle deviceRectangle = null;
+        if(vv != null) {
+            Dimension d = vv.getSize();
+            deviceRectangle = new Rectangle(
+                    0,0,
+                    d.width,d.height);
+        }
+        MutableTransformer vt = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW);
+        if(vt instanceof MutableTransformerDecorator) {
+        	vt = ((MutableTransformerDecorator)vt).getDelegate();
+        }
+        return vt.transform(s).intersects(deviceRectangle);
+    }
+
+    protected void paintShapeForVertex(RenderContext<V,E> rc, V v, Shape shape) {
+        GraphicsDecorator g = rc.getGraphicsContext();
+        Paint oldPaint = g.getPaint();
+        Paint fillPaint = rc.getVertexFillPaintTransformer().apply(v);
+        if(fillPaint != null) {
+            g.setPaint(fillPaint);
+            g.fill(shape);
+            g.setPaint(oldPaint);
+        }
+        Paint drawPaint = rc.getVertexDrawPaintTransformer().apply(v);
+        if(drawPaint != null) {
+        	g.setPaint(drawPaint);
+        	Stroke oldStroke = g.getStroke();
+        	Stroke stroke = rc.getVertexStrokeTransformer().apply(v);
+        	if(stroke != null) {
+        		g.setStroke(stroke);
+        	}
+        	g.draw(shape);
+        	g.setPaint(oldPaint);
+        	g.setStroke(oldStroke);
+        }
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/CachingEdgeRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/CachingEdgeRenderer.java
new file mode 100644
index 0000000..65eb058
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/CachingEdgeRenderer.java
@@ -0,0 +1,180 @@
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Dimension;
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.JComponent;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.visualization.BasicVisualizationServer;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.layout.LayoutChangeListener;
+import edu.uci.ics.jung.visualization.layout.LayoutEvent;
+import edu.uci.ics.jung.visualization.layout.LayoutEventSupport;
+import edu.uci.ics.jung.visualization.transform.LensTransformer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
+
+public class CachingEdgeRenderer<V, E> extends BasicEdgeRenderer<V, E>  
+	implements ChangeListener, LayoutChangeListener<V,E> {
+	
+	protected Map<E,Shape> edgeShapeMap = new HashMap<E,Shape>();
+	protected Set<E> dirtyEdges = new HashSet<E>();
+	
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	public CachingEdgeRenderer(BasicVisualizationServer<V,E> vv) {
+		vv.getRenderContext().getMultiLayerTransformer().addChangeListener(this);
+		Layout<V,E> layout = vv.getGraphLayout();
+		if(layout instanceof LayoutEventSupport) {
+			((LayoutEventSupport)layout).addLayoutChangeListener(this);
+		}
+	}
+	/**
+     * Draws the edge <code>e</code>, whose endpoints are at <code>(x1,y1)</code>
+     * and <code>(x2,y2)</code>, on the graphics context <code>g</code>.
+     * The <code>Shape</code> provided by the <code>EdgeShapeFunction</code> instance
+     * is scaled in the x-direction so that its width is equal to the distance between
+     * <code>(x1,y1)</code> and <code>(x2,y2)</code>.
+     */
+    protected void drawSimpleEdge(RenderContext<V,E> rc, Layout<V,E> layout, E e) {
+    	
+    	int[] coords = new int[4];
+    	boolean[] loop = new boolean[1];
+    	
+    	Shape edgeShape = edgeShapeMap.get(e);
+    	if(edgeShape == null || dirtyEdges.contains(e)) {
+    		edgeShape = prepareFinalEdgeShape(rc, layout, e, coords, loop);
+    		edgeShapeMap.put(e, edgeShape);
+    		dirtyEdges.remove(e);
+    	}
+    	
+    	int x1 = coords[0];
+    	int y1 = coords[1];
+    	int x2 = coords[2];
+    	int y2 = coords[3];
+    	boolean isLoop = loop[0];
+        
+        GraphicsDecorator g = rc.getGraphicsContext();
+        Graph<V,E> graph = layout.getGraph();
+        boolean edgeHit = true;
+        boolean arrowHit = true;
+        Rectangle deviceRectangle = null;
+        JComponent vv = rc.getScreenDevice();
+        if(vv != null) {
+            Dimension d = vv.getSize();
+            deviceRectangle = new Rectangle(0,0,d.width,d.height);
+        }
+        MutableTransformer vt = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW);
+        if(vt instanceof LensTransformer) {
+        	vt = ((LensTransformer)vt).getDelegate();
+        }
+        edgeHit = vt.transform(edgeShape).intersects(deviceRectangle);
+
+        if(edgeHit == true) {
+            
+            Paint oldPaint = g.getPaint();
+            
+            // get Paints for filling and drawing
+            // (filling is done first so that drawing and label use same Paint)
+            Paint fill_paint = rc.getEdgeFillPaintTransformer().apply(e); 
+            if (fill_paint != null)
+            {
+                g.setPaint(fill_paint);
+                g.fill(edgeShape);
+            }
+            Paint draw_paint = rc.getEdgeDrawPaintTransformer().apply(e);
+            if (draw_paint != null)
+            {
+                g.setPaint(draw_paint);
+                g.draw(edgeShape);
+            }
+            
+            float scalex = (float)g.getTransform().getScaleX();
+            float scaley = (float)g.getTransform().getScaleY();
+            // see if arrows are too small to bother drawing
+            if(scalex < .3 || scaley < .3) return;
+            
+            if (rc.getEdgeArrowPredicate().apply(Context.<Graph<V,E>,E>getInstance(graph, e))) {
+            	
+                Stroke new_stroke = rc.getEdgeArrowStrokeTransformer().apply(e);
+                Stroke old_stroke = g.getStroke();
+                if (new_stroke != null)
+                    g.setStroke(new_stroke);
+
+                
+                Shape destVertexShape = 
+                    rc.getVertexShapeTransformer().apply(graph.getEndpoints(e).getSecond());
+
+                AffineTransform xf = AffineTransform.getTranslateInstance(x2, y2);
+                destVertexShape = xf.createTransformedShape(destVertexShape);
+                
+                arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(destVertexShape).intersects(deviceRectangle);
+                if(arrowHit) {
+                    
+                    AffineTransform at = 
+                        edgeArrowRenderingSupport.getArrowTransform(rc, edgeShape, destVertexShape);
+                    if(at == null) return;
+                    Shape arrow = rc.getEdgeArrowTransformer().apply(Context.<Graph<V,E>,E>getInstance(graph, e));
+                    arrow = at.createTransformedShape(arrow);
+                    g.setPaint(rc.getArrowFillPaintTransformer().apply(e));
+                    g.fill(arrow);
+                    g.setPaint(rc.getArrowDrawPaintTransformer().apply(e));
+                    g.draw(arrow);
+                }
+                if (graph.getEdgeType(e) == EdgeType.UNDIRECTED) {
+                    Shape vertexShape = 
+                        rc.getVertexShapeTransformer().apply(graph.getEndpoints(e).getFirst());
+                    xf = AffineTransform.getTranslateInstance(x1, y1);
+                    vertexShape = xf.createTransformedShape(vertexShape);
+                    
+                    arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(vertexShape).intersects(deviceRectangle);
+                    
+                    if(arrowHit) {
+                        AffineTransform at = edgeArrowRenderingSupport.getReverseArrowTransform(rc, edgeShape, vertexShape, !isLoop);
+                        if(at == null) return;
+                        Shape arrow = rc.getEdgeArrowTransformer().apply(Context.<Graph<V,E>,E>getInstance(graph, e));
+                        arrow = at.createTransformedShape(arrow);
+                        g.setPaint(rc.getArrowFillPaintTransformer().apply(e));
+                        g.fill(arrow);
+                        g.setPaint(rc.getArrowDrawPaintTransformer().apply(e));
+                        g.draw(arrow);
+                    }
+                }
+                // restore paint and stroke
+                if (new_stroke != null)
+                    g.setStroke(old_stroke);
+
+            }
+            
+            // restore old paint
+            g.setPaint(oldPaint);
+        }
+    }
+
+//	@Override
+	public void stateChanged(ChangeEvent evt) {
+		System.err.println("got change event "+evt);
+		edgeShapeMap.clear();
+		
+	}
+//	@Override
+	public void layoutChanged(LayoutEvent<V, E> evt) {
+		V v = evt.getVertex();
+		Graph<V,E> graph = evt.getGraph();
+		dirtyEdges.addAll(graph.getIncidentEdges(v));
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/CachingRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/CachingRenderer.java
new file mode 100644
index 0000000..fc44aee
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/CachingRenderer.java
@@ -0,0 +1,15 @@
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Shape;
+import java.util.HashMap;
+import java.util.Map;
+
+public class CachingRenderer<V,E> extends BasicRenderer<V,E> {
+	
+	protected Map<E,Shape> edgeShapeMap = new HashMap<E,Shape>();
+	
+	protected Map<V,Shape> vertexShapeMap = new HashMap<V,Shape>();
+	
+	
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/CachingVertexRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/CachingVertexRenderer.java
new file mode 100644
index 0000000..b3ad202
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/CachingVertexRenderer.java
@@ -0,0 +1,77 @@
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Shape;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.Icon;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.visualization.BasicVisualizationServer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.layout.LayoutChangeListener;
+import edu.uci.ics.jung.visualization.layout.LayoutEvent;
+import edu.uci.ics.jung.visualization.layout.LayoutEventSupport;
+import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
+
+public class CachingVertexRenderer<V, E> extends BasicVertexRenderer<V, E> 
+	implements ChangeListener, LayoutChangeListener<V,E> {
+	
+	protected Map<V,Shape> vertexShapeMap = new HashMap<V,Shape>();
+	
+	protected Set<V> dirtyVertices = new HashSet<V>();
+	
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	public CachingVertexRenderer(BasicVisualizationServer<V,E> vv) {
+		vv.getRenderContext().getMultiLayerTransformer().addChangeListener(this);
+		Layout<V,E> layout = vv.getGraphLayout();
+		if(layout instanceof LayoutEventSupport) {
+			((LayoutEventSupport)layout).addLayoutChangeListener(this);
+			
+		}
+	}
+	
+    /**
+     * Paint <code>v</code>'s icon on <code>g</code> at <code>(x,y)</code>.
+     */
+    protected void paintIconForVertex(RenderContext<V,E> rc, V v, Layout<V,E> layout) {
+        GraphicsDecorator g = rc.getGraphicsContext();
+        boolean vertexHit = true;
+        int[] coords = new int[2];
+        Shape shape = vertexShapeMap.get(v);
+        if(shape == null || dirtyVertices.contains(v)) {
+        	shape = prepareFinalVertexShape(rc, v, layout, coords);
+        	vertexShapeMap.put(v, shape);
+        	dirtyVertices.remove(v);
+        }
+        vertexHit = vertexHit(rc, shape);
+        if (vertexHit) {
+        	if(rc.getVertexIconTransformer() != null) {
+        		Icon icon = rc.getVertexIconTransformer().apply(v);
+        		if(icon != null) {
+        		
+           			g.draw(icon, rc.getScreenDevice(), shape, coords[0], coords[1]);
+
+        		} else {
+        			paintShapeForVertex(rc, v, shape);
+        		}
+        	} else {
+        		paintShapeForVertex(rc, v, shape);
+        	}
+        }
+    }
+
+	public void stateChanged(ChangeEvent evt) {
+		System.err.println("got change event "+evt);
+		vertexShapeMap.clear();
+		
+	}
+
+	public void layoutChanged(LayoutEvent<V,E> evt) {
+		this.dirtyVertices.add(evt.getVertex());
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/CenterEdgeArrowRenderingSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/CenterEdgeArrowRenderingSupport.java
new file mode 100644
index 0000000..90206ba
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/CenterEdgeArrowRenderingSupport.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.RenderContext;
+
+public class CenterEdgeArrowRenderingSupport<V,E> implements EdgeArrowRenderingSupport<V,E> {
+
+    public AffineTransform getArrowTransform(RenderContext<V,E> rc, Shape edgeShape, Shape vertexShape) {
+    	GeneralPath path = new GeneralPath(edgeShape);
+        float[] seg = new float[6];
+        Point2D p1=null;
+        Point2D p2=null;
+        AffineTransform at = new AffineTransform();
+        // count the segments.
+        int middleSegment = 0;
+        int current = 0;
+        for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) {
+        	current++;
+        }
+        middleSegment = current/2;
+        // find the middle segment
+        current = 0;
+        for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) {
+        	current++;
+        	int ret = i.currentSegment(seg);
+        	if(ret == PathIterator.SEG_MOVETO) {
+        		p2 = new Point2D.Float(seg[0],seg[1]);
+        	} else if(ret == PathIterator.SEG_LINETO) {
+        		p1 = p2;
+        		p2 = new Point2D.Float(seg[0],seg[1]);
+        	}
+        	if(current > middleSegment) { // done
+        		at = getArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape);
+        		break;
+        	}
+
+        } 
+        return at;
+    }
+
+    public AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, Shape edgeShape, Shape vertexShape) {
+        return getReverseArrowTransform(rc, edgeShape, vertexShape, true);
+    }
+            
+    /**
+     * Returns a transform to position the arrowhead on this edge shape at the
+     * point where it intersects the passed vertex shape.
+     * 
+     * @param rc the rendering context used for rendering the arrow
+     * @param edgeShape the shape used to draw the edge
+     * @param vertexShape the shape used to draw the vertex
+     * @param passedGo (ignored in this implementation)
+     */
+    public AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, Shape edgeShape, Shape vertexShape,
+            boolean passedGo) {
+    	GeneralPath path = new GeneralPath(edgeShape);
+        float[] seg = new float[6];
+        Point2D p1=null;
+        Point2D p2=null;
+        AffineTransform at = new AffineTransform();
+        // count the segments.
+        int middleSegment = 0;
+        int current = 0;
+        for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) {
+        	current++;
+        }
+        middleSegment = current/2;
+        // find the middle segment
+        current = 0;
+        for(PathIterator i=path.getPathIterator(null,1); !i.isDone(); i.next()) {
+        	current++;
+        	int ret = i.currentSegment(seg);
+        	if(ret == PathIterator.SEG_MOVETO) {
+        		p2 = new Point2D.Float(seg[0],seg[1]);
+        	} else if(ret == PathIterator.SEG_LINETO) {
+        		p1 = p2;
+        		p2 = new Point2D.Float(seg[0],seg[1]);
+        	}
+        	if(current > middleSegment) { // done
+        		at = getReverseArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape);
+        		break;
+        	}
+        }
+        return at;
+    }
+
+    public AffineTransform getArrowTransform(RenderContext<V,E> rc, Line2D edgeShape, Shape vertexShape) {
+        
+        // find the midpoint of the edgeShape line, and use it to make the transform
+        Line2D left = new Line2D.Float();
+        Line2D right = new Line2D.Float();
+        this.subdivide(edgeShape, left, right);
+        edgeShape = right;
+        float dx = (float) (edgeShape.getX1()-edgeShape.getX2());
+        float dy = (float) (edgeShape.getY1()-edgeShape.getY2());
+        double atheta = Math.atan2(dx,dy)+Math.PI/2;
+        AffineTransform at = 
+            AffineTransform.getTranslateInstance(edgeShape.getX1(), edgeShape.getY1());
+        at.rotate(-atheta);
+        return at;
+    }
+
+    protected AffineTransform getReverseArrowTransform(RenderContext<V,E> rc,
+    		Line2D edgeShape, Shape vertexShape) {
+        // find the midpoint of the edgeShape line, and use it to make the transform
+        Line2D left = new Line2D.Float();
+        Line2D right = new Line2D.Float();
+        this.subdivide(edgeShape, left, right);
+        edgeShape = right;
+        float dx = (float) (edgeShape.getX1()-edgeShape.getX2());
+        float dy = (float) (edgeShape.getY1()-edgeShape.getY2());
+        // calculate the angle for the arrowhead
+        double atheta = Math.atan2(dx,dy)-Math.PI/2;
+        AffineTransform at = AffineTransform.getTranslateInstance(edgeShape.getX1(),edgeShape.getY1());
+        at.rotate(-atheta);
+        return at;
+    }
+    
+    /**
+     * divide a Line2D into 2 new Line2Ds that are returned
+     * in the passed left and right instances, if non-null
+     * @param src the line to divide
+     * @param left the left side, or null
+     * @param right the right side, or null
+     */
+    protected void subdivide(Line2D src,
+            Line2D left,
+            Line2D right) {
+        double x1 = src.getX1();
+        double y1 = src.getY1();
+        double x2 = src.getX2();
+        double y2 = src.getY2();
+        
+        double mx = x1 + (x2-x1)/2.0;
+        double my = y1 + (y2-y1)/2.0;
+        if (left != null) {
+            left.setLine(x1, y1, mx, my);
+        }
+        if (right != null) {
+            right.setLine(mx, my, x2, y2);
+        }
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/Checkmark.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/Checkmark.java
new file mode 100644
index 0000000..a6e1495
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/Checkmark.java
@@ -0,0 +1,63 @@
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.util.Collections;
+
+import javax.swing.Icon;
+
+/**
+ * a simple Icon that draws a checkmark in the lower-right quadrant of its
+ * area. Used to draw a checkmark on Picked Vertices.
+ * @author Tom Nelson
+ */
+public class Checkmark implements Icon {
+
+	GeneralPath path = new GeneralPath();
+	AffineTransform highlight = AffineTransform.getTranslateInstance(-1,-1);
+	AffineTransform lowlight = AffineTransform.getTranslateInstance(1,1);
+	AffineTransform shadow = AffineTransform.getTranslateInstance(2,2);
+	Color color;
+	public Checkmark() {
+		this(Color.green);
+	}
+	public Checkmark(Color color) {
+		this.color = color;
+		path.moveTo(10,17);
+		path.lineTo(13,20);
+		path.lineTo(20,13);
+	}
+	public void paintIcon(Component c, Graphics g, int x, int y) {
+		Shape shape = AffineTransform.getTranslateInstance(x, y).createTransformedShape(path);
+		Graphics2D g2d = (Graphics2D)g;
+		g2d.addRenderingHints(Collections.singletonMap(RenderingHints.KEY_ANTIALIASING, 
+				RenderingHints.VALUE_ANTIALIAS_ON));
+		Stroke stroke = g2d.getStroke();
+		g2d.setStroke(new BasicStroke(4));
+		g2d.setColor(Color.darkGray);
+		g2d.draw(shadow.createTransformedShape(shape));
+		g2d.setColor(Color.black);
+		g2d.draw(lowlight.createTransformedShape(shape));
+		g2d.setColor(Color.white);
+		g2d.draw(highlight.createTransformedShape(shape));
+		g2d.setColor(color);
+		g2d.draw(shape);
+		g2d.setStroke(stroke);
+	}
+
+	public int getIconWidth() {
+		return 20;
+	}
+
+	public int getIconHeight() {
+		return 20;
+	}
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/DefaultEdgeLabelRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/DefaultEdgeLabelRenderer.java
new file mode 100644
index 0000000..f89c058
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/DefaultEdgeLabelRenderer.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Apr 14, 2005
+ */
+
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.Rectangle;
+import java.io.Serializable;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+
+/**
+ * DefaultEdgeLabelRenderer is similar to the cell renderers
+ * used by the JTable and JTree jfc classes.
+ * 
+ * @author Tom Nelson 
+ *
+ * 
+ */
+ at SuppressWarnings("serial")
+public class DefaultEdgeLabelRenderer extends JLabel implements
+        EdgeLabelRenderer, Serializable {
+
+     protected static Border noFocusBorder = new EmptyBorder(0,0,0,0); 
+    
+     protected Color pickedEdgeLabelColor = Color.black;
+     protected boolean rotateEdgeLabels;
+     
+     public DefaultEdgeLabelRenderer(Color pickedEdgeLabelColor) {
+         this(pickedEdgeLabelColor, true);
+     }
+     
+    /**
+     * Creates an instance with the specified properties.
+     * 
+     * @param pickedEdgeLabelColor the color to use for rendering the labels of picked edges
+     * @param rotateEdgeLabels whether the 
+     */
+    public DefaultEdgeLabelRenderer(Color pickedEdgeLabelColor, boolean rotateEdgeLabels) {
+        super();
+        this.pickedEdgeLabelColor = pickedEdgeLabelColor;
+        this.rotateEdgeLabels = rotateEdgeLabels;
+        setOpaque(true);
+        setBorder(noFocusBorder);
+    }
+
+    /**
+     * @return Returns the rotateEdgeLabels.
+     */
+    public boolean isRotateEdgeLabels() {
+        return rotateEdgeLabels;
+    }
+    /**
+     * @param rotateEdgeLabels The rotateEdgeLabels to set.
+     */
+    public void setRotateEdgeLabels(boolean rotateEdgeLabels) {
+        this.rotateEdgeLabels = rotateEdgeLabels;
+    }
+    /**
+     * Overrides <code>JComponent.setForeground</code> to assign
+     * the unselected-foreground color to the specified color.
+     * 
+     * @param c set the foreground color to this value
+     */
+    @Override
+    public void setForeground(Color c) {
+        super.setForeground(c); 
+    }
+    
+    /**
+     * Overrides <code>JComponent.setBackground</code> to assign
+     * the unselected-background color to the specified color.
+     *
+     * @param c set the background color to this value
+     */
+    @Override
+    public void setBackground(Color c) {
+        super.setBackground(c); 
+    }
+
+    /**
+     * Notification from the <code>UIManager</code> that the look and feel
+     * has changed.
+     * Replaces the current UI object with the latest version from the 
+     * <code>UIManager</code>.
+     *
+     * @see JComponent#updateUI
+     */
+    @Override
+    public void updateUI() {
+        super.updateUI(); 
+        setForeground(null);
+        setBackground(null);
+    }
+    
+    /**
+    *
+    * Returns the default label renderer for an Edge
+    *
+    * @param vv  the <code>VisualizationViewer</code> to render on
+    * @param value  the value to assign to the label for
+    *			<code>Edge</code>
+    * @param edge  the <code>Edge</code>
+    * @return the default label renderer
+    */
+    public <E> Component getEdgeLabelRendererComponent(JComponent vv, Object value,
+            Font font, boolean isSelected, E edge) {
+        
+        super.setForeground(vv.getForeground());
+        if(isSelected) setForeground(pickedEdgeLabelColor);
+        super.setBackground(vv.getBackground());
+        
+        if(font != null) {
+            setFont(font);
+        } else {
+            setFont(vv.getFont());
+        }
+        setIcon(null);
+        setBorder(noFocusBorder);
+        setValue(value); 
+        return this;
+    }
+    
+    /*
+     * <bold id="override">Implementation Note</bold>
+     * The following methods are overridden as a performance measure to 
+     * prune code-paths that are often called in the case of renders
+     * but which we know are unnecessary.  Great care should be taken
+     * when writing your own renderer to weigh the benefits and 
+     * drawbacks of overriding methods like these.
+     */
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public boolean isOpaque() { 
+        Color back = getBackground();
+        Component p = getParent(); 
+        if (p != null) { 
+            p = p.getParent(); 
+        }
+        boolean colorMatch =
+        		(back != null) && (p != null)
+        		&& back.equals(p.getBackground()) && p.isOpaque();
+        return !colorMatch && super.isOpaque(); 
+    }
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void validate() {}
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void revalidate() {}
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void repaint(long tm, int x, int y, int width, int height) {}
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void repaint(Rectangle r) { }
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {	
+        // Strings get interned...
+        if (propertyName=="text") {
+            super.firePropertyChange(propertyName, oldValue, newValue);
+        }
+    }
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { }
+
+    /**
+     * Sets the <code>String</code> object for the cell being rendered to
+     * <code>value</code>.
+     * 
+     * @param value  the string value for this cell; if value is
+     *		<code>null</code> it sets the text value to an empty string
+     * @see JLabel#setText
+     * 
+     */
+    protected void setValue(Object value) {
+        setText((value == null) ? "" : value.toString());
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/DefaultVertexLabelRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/DefaultVertexLabelRenderer.java
new file mode 100644
index 0000000..6a592d2
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/DefaultVertexLabelRenderer.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Apr 14, 2005
+ */
+
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.Rectangle;
+import java.io.Serializable;
+
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+
+/**
+ * DefaultVertexLabelRenderer is similar to the cell renderers
+ * used by the JTable and JTree JFC classes.
+ * 
+ * @author Tom Nelson
+ */
+ at SuppressWarnings("serial")
+public class DefaultVertexLabelRenderer extends JLabel implements
+        VertexLabelRenderer, Serializable {
+
+     protected static Border noFocusBorder = new EmptyBorder(0,0,0,0); 
+    
+     protected Color pickedVertexLabelColor = Color.black;
+     
+    /**
+     * Creates a default table cell renderer.
+     * 
+     * @param pickedVertexLabelColor the color to use for rendering the labels of picked vertices
+     */
+    public DefaultVertexLabelRenderer(Color pickedVertexLabelColor) {
+        this.pickedVertexLabelColor = pickedVertexLabelColor;
+        setOpaque(true);
+        setBorder(noFocusBorder);
+    }
+
+    /**
+     * Overrides <code>JComponent.setForeground</code> to assign
+     * the unselected-foreground color to the specified color.
+     * 
+     * @param c set the foreground color to this value
+     */
+    @Override
+    public void setForeground(Color c) {
+        super.setForeground(c); 
+    }
+    
+    /**
+     * Overrides <code>JComponent.setBackground</code> to assign
+     * the unselected-background color to the specified color.
+     *
+     * @param c set the background color to this value
+     */
+    @Override
+    public void setBackground(Color c) {
+        super.setBackground(c); 
+    }
+
+    /**
+     * Notification from the <code>UIManager</code> that the look and feel
+     * has changed.
+     * Replaces the current UI object with the latest version from the 
+     * <code>UIManager</code>.
+     *
+     * @see JComponent#updateUI
+     */
+    @Override
+    public void updateUI() {
+        super.updateUI(); 
+        setForeground(null);
+        setBackground(null);
+    }
+    
+    /**
+     *
+     * Returns the default label renderer for a Vertex
+     *
+     * @param vv  the <code>VisualizationViewer</code> to render on
+     * @param value  the value to assign to the label for
+     *			<code>Vertex</code>
+     * @param vertex  the <code>Vertex</code>
+     * @return the default label renderer
+     */
+    public <V> Component getVertexLabelRendererComponent(JComponent vv, Object value,
+            Font font, boolean isSelected, V vertex) {
+        
+        super.setForeground(vv.getForeground());
+        if(isSelected) setForeground(pickedVertexLabelColor);
+        super.setBackground(vv.getBackground());
+        if(font != null) {
+            setFont(font);
+        } else {
+            setFont(vv.getFont());
+        }
+        setIcon(null);
+        setBorder(noFocusBorder);
+        setValue(value); 
+        return this;
+    }
+    
+    /*
+     * The following methods are overridden as a performance measure to 
+     * to prune code-paths are often called in the case of renders
+     * but which we know are unnecessary.  Great care should be taken
+     * when writing your own renderer to weigh the benefits and 
+     * drawbacks of overriding methods like these.
+     */
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public boolean isOpaque() { 
+        Color back = getBackground();
+        Component p = getParent(); 
+        if (p != null) { 
+            p = p.getParent(); 
+        }
+        boolean colorMatch = (back != null) && (p != null) && 
+        back.equals(p.getBackground()) && 
+        p.isOpaque();
+        return !colorMatch && super.isOpaque(); 
+    }
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void validate() {}
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void revalidate() {}
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void repaint(long tm, int x, int y, int width, int height) {}
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void repaint(Rectangle r) { }
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {	
+        // Strings get interned...
+        if (propertyName=="text") {
+            super.firePropertyChange(propertyName, oldValue, newValue);
+        }
+    }
+
+    /**
+     * Overridden for performance reasons.
+     * See the <a href="#override">Implementation Note</a> 
+     * for more information.
+     */
+    @Override
+    public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { }
+
+    /**
+     * Sets the <code>String</code> object for the cell being rendered to
+     * <code>value</code>.
+     * 
+     * @param value  the string value for this cell; if value is
+     *		<code>null</code> it sets the text value to an empty string
+     * @see JLabel#setText
+     * 
+     */
+    protected void setValue(Object value) {
+        setText((value == null) ? "" : value.toString());
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/EdgeArrowRenderingSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/EdgeArrowRenderingSupport.java
new file mode 100644
index 0000000..5990d99
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/EdgeArrowRenderingSupport.java
@@ -0,0 +1,63 @@
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Line2D;
+
+import edu.uci.ics.jung.visualization.RenderContext;
+
+public interface EdgeArrowRenderingSupport<V, E> {
+
+	/**
+     * Returns a transform to position the arrowhead on this edge shape at the
+     * point where it intersects the passed vertex shape.
+     * 
+     * @param rc the rendering context used for rendering the arrow
+	 * @param edgeShape the shape used to draw the edge
+     * @param vertexShape the shape used to draw the vertex
+     * @return a transform used for positioning the arrowhead for this vertex and edge
+	 */
+	AffineTransform getArrowTransform(RenderContext<V, E> rc,
+			Shape edgeShape, Shape vertexShape);
+
+	/**
+     * Returns a transform to position the arrowhead on this edge shape at the
+     * point where it intersects the passed vertex shape.
+     * 
+     * @param rc the rendering context used for rendering the arrow
+	 * @param edgeShape the shape used to draw the edge
+     * @param vertexShape the shape used to draw the vertex
+     * @return a transform used for positioning the arrowhead for this vertex and edge
+	 */
+	AffineTransform getReverseArrowTransform(
+			RenderContext<V, E> rc, Shape edgeShape, Shape vertexShape);
+
+    /**
+     * Returns a transform to position the arrowhead on this edge shape at the
+     * point where it intersects the passed vertex shape.
+     * 
+	 * <p>The Loop edge is a special case because its starting point is not inside
+	 * the vertex. The passedGo flag handles this case.
+	 * 
+     * @param rc the rendering context used for rendering the arrow
+     * @param edgeShape the shape used to draw the edge
+     * @param vertexShape the shape used to draw the vertex
+     * @param passedGo used for rendering loop edges
+     * @return a transform used for positioning the arrowhead for this vertex and edge
+     */
+	AffineTransform getReverseArrowTransform(
+			RenderContext<V, E> rc, Shape edgeShape, Shape vertexShape,
+			boolean passedGo);
+
+	/**
+     * Returns a transform to position the arrowhead on this edge shape at the
+     * point where it intersects the passed vertex shape.
+     * 
+     * @param rc the rendering context used for rendering the arrow
+	 * @param edgeShape the shape used to draw the edge
+     * @param vertexShape the shape used to draw the vertex
+     * @return a transform used for positioning the arrowhead for this vertex and edge
+	 */
+	AffineTransform getArrowTransform(RenderContext<V, E> rc,
+			Line2D edgeShape, Shape vertexShape);
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/EdgeLabelRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/EdgeLabelRenderer.java
new file mode 100644
index 0000000..13ab776
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/EdgeLabelRenderer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Apr 14, 2005
+ */
+
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Component;
+import java.awt.Font;
+
+import javax.swing.JComponent;
+
+/**
+ * @author Tom Nelson
+ *
+ * 
+ */
+public interface EdgeLabelRenderer {
+	/**
+     * Returns the component used for drawing the label.  This method is
+     * used to configure the renderer appropriately before drawing.
+	 * 
+	 * @param component the component that is asking the renderer to draw
+	 * @param value the value of the cell to be rendered; the details of how to
+	 * 		render the value are up to the renderer implementation.  For example,
+	 * 		if {@code value} is the string "true", it could be rendered as the
+	 * 		string or as a checked checkbox.  
+	 * @param font the font to use in rendering the label
+	 * @param isSelected whether the edge is currently selected
+	 * @param edge the edge whose label is being drawn
+	 * @param <E> the edge type
+	 * @return the component used for drawing the label
+	 */
+    <E> Component getEdgeLabelRendererComponent(JComponent component,
+    		Object value, Font font, boolean isSelected, E edge);
+    
+    boolean isRotateEdgeLabels();
+    
+    void setRotateEdgeLabels(boolean state);
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/GradientVertexRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/GradientVertexRenderer.java
new file mode 100644
index 0000000..32b75f0
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/GradientVertexRenderer.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.GradientPaint;
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+
+import javax.swing.JComponent;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
+
+/**
+ * A renderer that will fill vertex shapes with a GradientPaint
+ * @author Tom Nelson
+ *
+ * @param <V> the vertex type
+ * @param <V> the edge type
+ */
+public class GradientVertexRenderer<V,E> implements Renderer.Vertex<V,E> {
+	
+	Color colorOne;
+	Color colorTwo;
+	Color pickedColorOne;
+	Color pickedColorTwo;
+	PickedState<V> pickedState;
+	boolean cyclic;
+	
+
+    public GradientVertexRenderer(Color colorOne, Color colorTwo, boolean cyclic) {
+		this.colorOne = colorOne;
+		this.colorTwo = colorTwo;
+		this.cyclic = cyclic;
+	}
+
+
+	public GradientVertexRenderer(Color colorOne, Color colorTwo, Color pickedColorOne, Color pickedColorTwo, PickedState<V> pickedState, boolean cyclic) {
+		this.colorOne = colorOne;
+		this.colorTwo = colorTwo;
+		this.pickedColorOne = pickedColorOne;
+		this.pickedColorTwo = pickedColorTwo;
+		this.pickedState = pickedState;
+		this.cyclic = cyclic;
+	}
+
+
+	public void paintVertex(RenderContext<V,E> rc, Layout<V,E> layout, V v) {
+		Graph<V,E> graph = layout.getGraph();
+        if (rc.getVertexIncludePredicate().apply(Context.<Graph<V,E>,V>getInstance(graph,v))) {
+            boolean vertexHit = true;
+            // get the shape to be rendered
+            Shape shape = rc.getVertexShapeTransformer().apply(v);
+            
+            Point2D p = layout.apply(v);
+            p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p);
+
+            float x = (float)p.getX();
+            float y = (float)p.getY();
+
+            // create a transform that translates to the location of
+            // the vertex to be rendered
+            AffineTransform xform = AffineTransform.getTranslateInstance(x,y);
+            // transform the vertex shape with xtransform
+            shape = xform.createTransformedShape(shape);
+            
+            vertexHit = vertexHit(rc, shape);
+                //rc.getViewTransformer().transform(shape).intersects(deviceRectangle);
+
+            if (vertexHit) {
+            	paintShapeForVertex(rc, v, shape);
+            }
+        }
+    }
+    
+    protected boolean vertexHit(RenderContext<V,E> rc, Shape s) {
+        JComponent vv = rc.getScreenDevice();
+        Rectangle deviceRectangle = null;
+        if(vv != null) {
+            Dimension d = vv.getSize();
+            deviceRectangle = new Rectangle(
+                    0,0,
+                    d.width,d.height);
+        }
+        return rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(s).intersects(deviceRectangle);
+    }
+
+    protected void paintShapeForVertex(RenderContext<V,E> rc, V v, Shape shape) {
+        GraphicsDecorator g = rc.getGraphicsContext();
+        Paint oldPaint = g.getPaint();
+        Rectangle r = shape.getBounds();
+        float y2 = (float)r.getMaxY();
+        if(cyclic) {
+        	y2 = (float)(r.getMinY()+r.getHeight()/2);
+        }
+        
+        Paint fillPaint = null;
+        if(pickedState != null && pickedState.isPicked(v)) {
+        	fillPaint = new GradientPaint((float)r.getMinX(), (float)r.getMinY(), pickedColorOne,
+            		(float)r.getMinX(), y2, pickedColorTwo, cyclic);
+        } else {
+        	fillPaint = new GradientPaint((float)r.getMinX(), (float)r.getMinY(), colorOne,
+        		(float)r.getMinX(), y2, colorTwo, cyclic);
+        }
+        if(fillPaint != null) {
+            g.setPaint(fillPaint);
+            g.fill(shape);
+            g.setPaint(oldPaint);
+        }
+        Paint drawPaint = rc.getVertexDrawPaintTransformer().apply(v);
+        if(drawPaint != null) {
+            g.setPaint(drawPaint);
+        }
+        Stroke oldStroke = g.getStroke();
+        Stroke stroke = rc.getVertexStrokeTransformer().apply(v);
+        if(stroke != null) {
+            g.setStroke(stroke);
+        }
+        g.draw(shape);
+        g.setPaint(oldPaint);
+        g.setStroke(oldStroke);
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/Renderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/Renderer.java
new file mode 100644
index 0000000..eb95522
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/Renderer.java
@@ -0,0 +1,92 @@
+/*
+* Copyright (c) 2003, The JUNG Authors 
+*
+* All rights reserved.
+*
+* This software is open-source under the BSD license; see either
+* "license.txt" or
+* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+*/
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Dimension;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.visualization.RenderContext;
+
+/**
+ * The interface for drawing vertices, edges, and their labels.
+ * Implementations of this class can set specific renderers for
+ * each element, allowing custom control of each.
+ */
+public interface Renderer<V,E> {
+
+	void render(RenderContext<V,E> rc, Layout<V,E> layout);
+	void renderVertex(RenderContext<V,E> rc, Layout<V,E> layout, V v);
+	void renderVertexLabel(RenderContext<V,E> rc, Layout<V,E> layout, V v);
+	void renderEdge(RenderContext<V,E> rc, Layout<V,E> layout, E e);
+	void renderEdgeLabel(RenderContext<V,E> rc, Layout<V,E> layout, E e);
+    void setVertexRenderer(Renderer.Vertex<V,E> r);
+    void setEdgeRenderer(Renderer.Edge<V,E> r);
+    void setVertexLabelRenderer(Renderer.VertexLabel<V,E> r);
+    void setEdgeLabelRenderer(Renderer.EdgeLabel<V,E> r);
+    Renderer.VertexLabel<V,E> getVertexLabelRenderer();
+    Renderer.Vertex<V,E> getVertexRenderer();
+    Renderer.Edge<V,E> getEdgeRenderer();
+    Renderer.EdgeLabel<V,E> getEdgeLabelRenderer();
+
+	interface Vertex<V,E> {
+		void paintVertex(RenderContext<V,E> rc, Layout<V,E> layout, V v);
+		@SuppressWarnings("rawtypes")
+		class NOOP implements Vertex {
+			public void paintVertex(RenderContext rc, Layout layout, Object v) {}
+		};
+	}
+    
+	interface Edge<V,E> {
+		void paintEdge(RenderContext<V,E> rc, Layout<V,E> layout, E e);
+		EdgeArrowRenderingSupport<V, E> getEdgeArrowRenderingSupport();
+		void setEdgeArrowRenderingSupport(EdgeArrowRenderingSupport<V, E> edgeArrowRenderingSupport);
+		@SuppressWarnings("rawtypes")
+		class NOOP implements Edge {
+			public void paintEdge(RenderContext rc, Layout layout, Object e) {}
+			public EdgeArrowRenderingSupport getEdgeArrowRenderingSupport(){return null;}
+			public void setEdgeArrowRenderingSupport(EdgeArrowRenderingSupport edgeArrowRenderingSupport){}
+		}
+	}
+	
+	interface VertexLabel<V,E> {
+		void labelVertex(RenderContext<V,E> rc, Layout<V,E> layout, V v, String label);
+		Position getPosition();
+		void setPosition(Position position);
+		void setPositioner(Positioner positioner);
+		Positioner getPositioner();
+		@SuppressWarnings("rawtypes")
+		class NOOP implements VertexLabel {
+			public void labelVertex(RenderContext rc, Layout layout, Object v, String label) {}
+			public Position getPosition() { return Position.CNTR; }
+			public void setPosition(Position position) {}
+			public Positioner getPositioner() {
+				return new Positioner() {
+					public Position getPosition(float x, float y, Dimension d) {
+						return Position.CNTR;
+					}};
+			}
+			public void setPositioner(Positioner positioner) {
+			}
+		}
+		enum Position { N, NE, E, SE, S, SW, W, NW, CNTR, AUTO }
+	    interface Positioner {
+	    	Position getPosition(float x, float y, Dimension d);
+	    }
+
+	}
+	
+	interface EdgeLabel<V,E> {
+		void labelEdge(RenderContext<V,E> rc, Layout<V,E> layout, E e, String label);
+		@SuppressWarnings("rawtypes")
+		class NOOP implements EdgeLabel {
+			public void labelEdge(RenderContext rc, Layout layout, Object e, String label) {}
+		}
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/ReshapingEdgeRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/ReshapingEdgeRenderer.java
new file mode 100644
index 0000000..5667d4b
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/ReshapingEdgeRenderer.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Dimension;
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RectangularShape;
+
+import javax.swing.JComponent;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.transform.LensTransformer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+import edu.uci.ics.jung.visualization.transform.shape.TransformingGraphics;
+
+/**
+ * uses a flatness argument to break edges into
+ * smaller segments. This produces a more detailed
+ * transformation of the edge shape
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ * @param <V> the vertex type
+ * @param <V> the edge type
+ */
+public class ReshapingEdgeRenderer<V,E> extends BasicEdgeRenderer<V,E>
+	implements Renderer.Edge<V,E> {
+
+    /**
+     * Draws the edge <code>e</code>, whose endpoints are at <code>(x1,y1)</code>
+     * and <code>(x2,y2)</code>, on the graphics context <code>g</code>.
+     * The <code>Shape</code> provided by the <code>EdgeShapeFunction</code> instance
+     * is scaled in the x-direction so that its width is equal to the distance between
+     * <code>(x1,y1)</code> and <code>(x2,y2)</code>.
+     */
+    protected void drawSimpleEdge(RenderContext<V,E> rc, Layout<V,E> layout, E e) {
+        
+    	TransformingGraphics g = (TransformingGraphics)rc.getGraphicsContext();
+        Graph<V,E> graph = layout.getGraph();
+        Pair<V> endpoints = graph.getEndpoints(e);
+        V v1 = endpoints.getFirst();
+        V v2 = endpoints.getSecond();
+        Point2D p1 = layout.apply(v1);
+        Point2D p2 = layout.apply(v2);
+        p1 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p1);
+        p2 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p2);
+        float x1 = (float) p1.getX();
+        float y1 = (float) p1.getY();
+        float x2 = (float) p2.getX();
+        float y2 = (float) p2.getY();
+        
+        float flatness = 0;
+        MutableTransformer transformer = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW);
+        if(transformer instanceof LensTransformer) {
+            LensTransformer ht = (LensTransformer)transformer;
+            RectangularShape lensShape = ht.getLensShape();
+            if(lensShape.contains(x1,y1) || lensShape.contains(x2,y2)) {
+                flatness = .05f;
+            }
+        }
+
+        boolean isLoop = v1.equals(v2);
+        Shape s2 = rc.getVertexShapeTransformer().apply(v2);
+        Shape edgeShape = rc.getEdgeShapeTransformer().apply(e);
+        
+        boolean edgeHit = true;
+        boolean arrowHit = true;
+        Rectangle deviceRectangle = null;
+        JComponent vv = rc.getScreenDevice();
+        if(vv != null) {
+            Dimension d = vv.getSize();
+            deviceRectangle = new Rectangle(0,0,d.width,d.height);
+        }
+
+        AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
+        
+        if(isLoop) {
+            // this is a self-loop. scale it is larger than the vertex
+            // it decorates and translate it so that its nadir is
+            // at the center of the vertex.
+            Rectangle2D s2Bounds = s2.getBounds2D();
+            xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight());
+            xform.translate(0, -edgeShape.getBounds2D().getWidth()/2);
+        } else {
+            // this is a normal edge. Rotate it to the angle between
+            // vertex endpoints, then scale it to the distance between
+            // the vertices
+            float dx = x2-x1;
+            float dy = y2-y1;
+            float thetaRadians = (float) Math.atan2(dy, dx);
+            xform.rotate(thetaRadians);
+            float dist = (float) Math.sqrt(dx*dx + dy*dy);
+            xform.scale(dist, 1.0);
+        }
+        
+        edgeShape = xform.createTransformedShape(edgeShape);
+        
+        MutableTransformer vt = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW);
+        if(vt instanceof LensTransformer) {
+        	vt = ((LensTransformer)vt).getDelegate();
+        }
+        edgeHit = vt.transform(edgeShape).intersects(deviceRectangle);
+
+        if(edgeHit == true) {
+            
+            Paint oldPaint = g.getPaint();
+            
+            // get Paints for filling and drawing
+            // (filling is done first so that drawing and label use same Paint)
+            Paint fill_paint = rc.getEdgeFillPaintTransformer().apply(e); 
+            if (fill_paint != null)
+            {
+                g.setPaint(fill_paint);
+                g.fill(edgeShape, flatness);
+            }
+            Paint draw_paint = rc.getEdgeDrawPaintTransformer().apply(e);
+            if (draw_paint != null)
+            {
+                g.setPaint(draw_paint);
+                g.draw(edgeShape, flatness);
+            }
+            
+            float scalex = (float)g.getTransform().getScaleX();
+            float scaley = (float)g.getTransform().getScaleY();
+            // see if arrows are too small to bother drawing
+            if(scalex < .3 || scaley < .3) return;
+            
+            if (rc.getEdgeArrowPredicate().apply(Context.<Graph<V,E>,E>getInstance(graph, e))) {
+                
+                Shape destVertexShape = 
+                    rc.getVertexShapeTransformer().apply(graph.getEndpoints(e).getSecond());
+
+                AffineTransform xf = AffineTransform.getTranslateInstance(x2, y2);
+                destVertexShape = xf.createTransformedShape(destVertexShape);
+                
+                arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(destVertexShape).intersects(deviceRectangle);
+                if(arrowHit) {
+                    
+                    AffineTransform at = 
+                        edgeArrowRenderingSupport.getArrowTransform(rc, new GeneralPath(edgeShape), destVertexShape);
+                    if(at == null) return;
+                    Shape arrow = rc.getEdgeArrowTransformer().apply(Context.<Graph<V,E>,E>getInstance(graph, e));
+                    arrow = at.createTransformedShape(arrow);
+                    g.setPaint(rc.getArrowFillPaintTransformer().apply(e));
+                    g.fill(arrow);
+                    g.setPaint(rc.getArrowDrawPaintTransformer().apply(e));
+                    g.draw(arrow);
+                }
+                if (graph.getEdgeType(e) == EdgeType.UNDIRECTED) {
+                    Shape vertexShape = 
+                        rc.getVertexShapeTransformer().apply(graph.getEndpoints(e).getFirst());
+                    xf = AffineTransform.getTranslateInstance(x1, y1);
+                    vertexShape = xf.createTransformedShape(vertexShape);
+                    
+                    arrowHit = rc.getMultiLayerTransformer().getTransformer(Layer.VIEW).transform(vertexShape).intersects(deviceRectangle);
+                    
+                    if(arrowHit) {
+                        AffineTransform at = edgeArrowRenderingSupport.getReverseArrowTransform(rc, new GeneralPath(edgeShape), vertexShape, !isLoop);
+                        if(at == null) return;
+                        Shape arrow = rc.getEdgeArrowTransformer().apply(Context.<Graph<V,E>,E>getInstance(graph, e));
+                        arrow = at.createTransformedShape(arrow);
+                        g.setPaint(rc.getArrowFillPaintTransformer().apply(e));
+                        g.fill(arrow);
+                        g.setPaint(rc.getArrowDrawPaintTransformer().apply(e));
+                        g.draw(arrow);
+                    }
+                }
+            }
+            // use existing paint for text if no draw paint specified
+            if (draw_paint == null)
+                g.setPaint(oldPaint);
+            
+            // restore old paint
+            g.setPaint(oldPaint);
+        }
+    }
+    
+    /**
+     * Returns a transform to position the arrowhead on this edge shape at the
+     * point where it intersects the passed vertex shape.
+     */
+//    public AffineTransform getArrowTransform(RenderContext<V,E> rc, GeneralPath edgeShape, Shape vertexShape) {
+//        float[] seg = new float[6];
+//        Point2D p1=null;
+//        Point2D p2=null;
+//        AffineTransform at = new AffineTransform();
+//        // when the PathIterator is done, switch to the line-subdivide
+//        // method to get the arrowhead closer.
+//        for(PathIterator i=edgeShape.getPathIterator(null,1); !i.isDone(); i.next()) {
+//            int ret = i.currentSegment(seg);
+//            if(ret == PathIterator.SEG_MOVETO) {
+//                p2 = new Point2D.Float(seg[0],seg[1]);
+//            } else if(ret == PathIterator.SEG_LINETO) {
+//                p1 = p2;
+//                p2 = new Point2D.Float(seg[0],seg[1]);
+//                if(vertexShape.contains(p2)) {
+//                    at = getArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape);
+//                    break;
+//                }
+//            } 
+//        }
+//        return at;
+//    }
+
+    /**
+     * Returns a transform to position the arrowhead on this edge shape at the
+     * point where it intersects the passed vertex shape.
+     */
+//    public AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, GeneralPath edgeShape, Shape vertexShape) {
+//        return getReverseArrowTransform(rc, edgeShape, vertexShape, true);
+//    }
+            
+    /**
+     * <p>Returns a transform to position the arrowhead on this edge shape at the
+     * point where it intersects the passed vertex shape.
+     * 
+     * <p>The Loop edge is a special case because its staring point is not inside
+     * the vertex. The passedGo flag handles this case.
+     * 
+     * @param edgeShape
+     * @param vertexShape
+     * @param passedGo - used only for Loop edges
+     */
+//    public AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, GeneralPath edgeShape, Shape vertexShape,
+//            boolean passedGo) {
+//        float[] seg = new float[6];
+//        Point2D p1=null;
+//        Point2D p2=null;
+//
+//        AffineTransform at = new AffineTransform();
+//        for(PathIterator i=edgeShape.getPathIterator(null,1); !i.isDone(); i.next()) {
+//            int ret = i.currentSegment(seg);
+//            if(ret == PathIterator.SEG_MOVETO) {
+//                p2 = new Point2D.Float(seg[0],seg[1]);
+//            } else if(ret == PathIterator.SEG_LINETO) {
+//                p1 = p2;
+//                p2 = new Point2D.Float(seg[0],seg[1]);
+//                if(passedGo == false && vertexShape.contains(p2)) {
+//                    passedGo = true;
+//                 } else if(passedGo==true &&
+//                        vertexShape.contains(p2)==false) {
+//                     at = getReverseArrowTransform(rc, new Line2D.Float(p1,p2),vertexShape);
+//                    break;
+//                }
+//            } 
+//        }
+//        return at;
+//    }
+
+    /**
+     * This is used for the arrow of a directed and for one of the
+     * arrows for non-directed edges
+     * Get a transform to place the arrow shape on the passed edge at the
+     * point where it intersects the passed shape
+     * @param edgeShape
+     * @param vertexShape
+     * @return
+     */
+//    public AffineTransform getArrowTransform(RenderContext<V,E> rc, Line2D edgeShape, Shape vertexShape) {
+//        float dx = (float) (edgeShape.getX1()-edgeShape.getX2());
+//        float dy = (float) (edgeShape.getY1()-edgeShape.getY2());
+//        // iterate over the line until the edge shape will place the
+//        // arrowhead closer than 'arrowGap' to the vertex shape boundary
+//        while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) {
+//            try {
+//                edgeShape = getLastOutsideSegment(edgeShape, vertexShape);
+//            } catch(IllegalArgumentException e) {
+//                System.err.println(e.toString());
+//                return null;
+//            }
+//            dx = (float) (edgeShape.getX1()-edgeShape.getX2());
+//            dy = (float) (edgeShape.getY1()-edgeShape.getY2());
+//        }
+//        double atheta = Math.atan2(dx,dy)+Math.PI/2;
+//        AffineTransform at = 
+//            AffineTransform.getTranslateInstance(edgeShape.getX1(), edgeShape.getY1());
+//        at.rotate(-atheta);
+//        return at;
+//    }
+
+    /**
+     * This is used for the reverse-arrow of a non-directed edge
+     * get a transform to place the arrow shape on the passed edge at the
+     * point where it intersects the passed shape
+     * @param edgeShape
+     * @param vertexShape
+     * @return
+     */
+//    protected AffineTransform getReverseArrowTransform(RenderContext<V,E> rc, Line2D edgeShape, Shape vertexShape) {
+//        float dx = (float) (edgeShape.getX1()-edgeShape.getX2());
+//        float dy = (float) (edgeShape.getY1()-edgeShape.getY2());
+//        // iterate over the line until the edge shape will place the
+//        // arrowhead closer than 'arrowGap' to the vertex shape boundary
+//        while((dx*dx+dy*dy) > rc.getArrowPlacementTolerance()) {
+//            try {
+//                edgeShape = getFirstOutsideSegment(edgeShape, vertexShape);
+//            } catch(IllegalArgumentException e) {
+//                System.err.println(e.toString());
+//                return null;
+//            }
+//            dx = (float) (edgeShape.getX1()-edgeShape.getX2());
+//            dy = (float) (edgeShape.getY1()-edgeShape.getY2());
+//        }
+//        // calculate the angle for the arrowhead
+//        double atheta = Math.atan2(dx,dy)-Math.PI/2;
+//        AffineTransform at = AffineTransform.getTranslateInstance(edgeShape.getX1(),edgeShape.getY1());
+//        at.rotate(-atheta);
+//        return at;
+//    }
+    
+    /**
+     * Passed Line's point2 must be inside the passed shape or
+     * an IllegalArgumentException is thrown
+     * @param line line to subdivide
+     * @param shape shape to compare with line
+     * @return a line that intersects the shape boundary
+     * @throws IllegalArgumentException if the passed line's point1 is not inside the shape
+     */
+//    protected Line2D getLastOutsideSegment(Line2D line, Shape shape) {
+//        if(shape.contains(line.getP2())==false) {
+//            String errorString =
+//                "line end point: "+line.getP2()+" is not contained in shape: "+shape.getBounds2D();
+//            throw new IllegalArgumentException(errorString);
+//            //return null;
+//        }
+//        Line2D left = new Line2D.Double();
+//        Line2D right = new Line2D.Double();
+//        // subdivide the line until its left segment intersects
+//        // the shape boundary
+//        do {
+//            subdivide(line, left, right);
+//            line = right;
+//        } while(shape.contains(line.getP1())==false);
+//        // now that right is completely inside shape,
+//        // return left, which must be partially outside
+//        return left;
+//    }
+   
+    /**
+     * Passed Line's point1 must be inside the passed shape or
+     * an IllegalArgumentException is thrown
+     * @param line line to subdivide
+     * @param shape shape to compare with line
+     * @return a line that intersects the shape boundary
+     * @throws IllegalArgumentException if the passed line's point1 is not inside the shape
+     */
+//    protected Line2D getFirstOutsideSegment(Line2D line, Shape shape) {
+//        
+//        if(shape.contains(line.getP1())==false) {
+//            String errorString = 
+//                "line start point: "+line.getP1()+" is not contained in shape: "+shape.getBounds2D();
+//            throw new IllegalArgumentException(errorString);
+//        }
+//        Line2D left = new Line2D.Float();
+//        Line2D right = new Line2D.Float();
+//        // subdivide the line until its right side intersects the
+//        // shape boundary
+//        do {
+//            subdivide(line, left, right);
+//            line = left;
+//        } while(shape.contains(line.getP2())==false);
+//        // now that left is completely inside shape,
+//        // return right, which must be partially outside
+//        return right;
+//    }
+
+    /**
+     * divide a Line2D into 2 new Line2Ds that are returned
+     * in the passed left and right instances, if non-null
+     * @param src the line to divide
+     * @param left the left side, or null
+     * @param right the right side, or null
+     */
+//    protected void subdivide(Line2D src,
+//            Line2D left,
+//            Line2D right) {
+//        double x1 = src.getX1();
+//        double y1 = src.getY1();
+//        double x2 = src.getX2();
+//        double y2 = src.getY2();
+//        
+//        double mx = x1 + (x2-x1)/2.0;
+//        double my = y1 + (y2-y1)/2.0;
+//        if (left != null) {
+//            left.setLine(x1, y1, mx, my);
+//        }
+//        if (right != null) {
+//            right.setLine(mx, my, x2, y2);
+//        }
+//    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/VertexLabelAsShapeRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/VertexLabelAsShapeRenderer.java
new file mode 100644
index 0000000..5aa5009
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/VertexLabelAsShapeRenderer.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.geom.Point2D;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Context;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
+
+/**
+ * Renders Vertex Labels, but can also supply Shapes for vertices.
+ * This has the effect of making the vertex label the actual vertex
+ * shape. The user will probably want to center the vertex label
+ * on the vertex location.
+ * 
+ * @author Tom Nelson
+ *
+ * @param <V> the vertex type
+ * @param <V> the edge type
+ */
+public class VertexLabelAsShapeRenderer<V,E> 
+	implements Renderer.VertexLabel<V,E>, Function<V,Shape> {
+
+	protected Map<V,Shape> shapes = new HashMap<V,Shape>();
+	protected RenderContext<V,E> rc;
+	
+	public VertexLabelAsShapeRenderer(RenderContext<V, E> rc) {
+		this.rc = rc;
+	}
+
+	public Component prepareRenderer(RenderContext<V,E> rc, VertexLabelRenderer graphLabelRenderer, Object value, 
+			boolean isSelected, V vertex) {
+		return rc.getVertexLabelRenderer().<V>getVertexLabelRendererComponent(rc.getScreenDevice(), value, 
+				rc.getVertexFontTransformer().apply(vertex), isSelected, vertex);
+	}
+
+	/**
+	 * Labels the specified vertex with the specified label.  
+	 * Uses the font specified by this instance's 
+	 * <code>VertexFontFunction</code>.  (If the font is unspecified, the existing
+	 * font for the graphics context is used.)  If vertex label centering
+	 * is active, the label is centered on the position of the vertex; otherwise
+     * the label is offset slightly.
+     */
+    public void labelVertex(RenderContext<V,E> rc, Layout<V,E> layout, V v, String label) {
+    	Graph<V,E> graph = layout.getGraph();
+        if (rc.getVertexIncludePredicate().apply(Context.<Graph<V,E>,V>getInstance(graph,v)) == false) {
+        	return;
+        }
+        GraphicsDecorator g = rc.getGraphicsContext();
+        Component component = prepareRenderer(rc, rc.getVertexLabelRenderer(), label,
+        		rc.getPickedVertexState().isPicked(v), v);
+        Dimension d = component.getPreferredSize();
+        
+        int h_offset = -d.width / 2;
+        int v_offset = -d.height / 2;
+        
+        Point2D p = layout.apply(v);
+        p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p);
+
+        int x = (int)p.getX();
+        int y = (int)p.getY();
+
+        g.draw(component, rc.getRendererPane(), x+h_offset, y+v_offset, d.width, d.height, true);
+
+        Dimension size = component.getPreferredSize();
+        Rectangle bounds = new Rectangle(-size.width/2 -2, -size.height/2 -2, size.width+4, size.height);
+        shapes.put(v, bounds);
+    }
+
+	public Shape apply(V v) {
+		Component component = prepareRenderer(rc, rc.getVertexLabelRenderer(), rc.getVertexLabelTransformer().apply(v),
+				rc.getPickedVertexState().isPicked(v), v);
+        Dimension size = component.getPreferredSize();
+        Rectangle bounds = new Rectangle(-size.width/2 -2, -size.height/2 -2, size.width+4, size.height);
+        return bounds;
+//		Shape shape = shapes.get(v);
+//		if(shape == null) {
+//			return new Rectangle(-20,-20,40,40);
+//		}
+//		else return shape;
+	}
+
+	public Renderer.VertexLabel.Position getPosition() {
+		return Renderer.VertexLabel.Position.CNTR;
+	}
+
+	public Renderer.VertexLabel.Positioner getPositioner() {
+		return new Positioner() {
+			public Renderer.VertexLabel.Position getPosition(float x, float y, Dimension d) {
+				return Renderer.VertexLabel.Position.CNTR;
+			}};
+	}
+
+	public void setPosition(Renderer.VertexLabel.Position position) {
+		// noop
+	}
+
+	public void setPositioner(Renderer.VertexLabel.Positioner positioner) {
+		//noop
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/VertexLabelRenderer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/VertexLabelRenderer.java
new file mode 100644
index 0000000..ecbf8a2
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/VertexLabelRenderer.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Apr 14, 2005
+ */
+
+package edu.uci.ics.jung.visualization.renderers;
+
+import java.awt.Component;
+import java.awt.Font;
+
+import javax.swing.JComponent;
+
+/**
+ * @author Tom Nelson 
+ *
+ * 
+ */
+public interface VertexLabelRenderer {
+	/**
+     * Returns the component used for drawing the label.  This method is
+     * used to configure the renderer appropriately before drawing.
+	 * 
+	 * @param vv the component that is asking the renderer to draw
+	 * @param value the value of the cell to be rendered; the details of how to
+	 * 		render the value are up to the renderer implementation.  For example,
+	 * 		if {@code value} is the string "true", it could be rendered as the
+	 * 		string or as a checked checkbox.  
+	 * @param font the font to use in rendering the label
+	 * @param isSelected whether the vertex is currently selected
+	 * @param vertex the edge whose label is being drawn
+	 * @param <V> the vertex type
+	 * @return the component used for drawing the label
+	 */
+    <V> Component getVertexLabelRendererComponent(JComponent vv, Object value,
+					   Font font, boolean isSelected, V vertex);
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/package.html b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/package.html
new file mode 100644
index 0000000..39f120e
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/renderers/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Visualization mechanisms relating to rendering.
+
+</body>
+</html>
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/spatial/AggregateGraph.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/spatial/AggregateGraph.java
new file mode 100644
index 0000000..7a34714
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/spatial/AggregateGraph.java
@@ -0,0 +1,271 @@
+package edu.uci.ics.jung.visualization.spatial;
+
+import java.util.Collection;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+
+public class AggregateGraph<V, E> implements Graph<V, E> {
+
+	
+//	@Override
+	public boolean addEdge(E e, V v1, V v2) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public V getDest(E directedEdge) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public Pair<V> getEndpoints(E edge) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public Collection<E> getInEdges(V vertex) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public V getOpposite(V vertex, E edge) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public Collection<E> getOutEdges(V vertex) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public int getPredecessorCount(V vertex) {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+//	@Override
+	public Collection<V> getPredecessors(V vertex) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public V getSource(E directedEdge) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public int getSuccessorCount(V vertex) {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+//	@Override
+	public Collection<V> getSuccessors(V vertex) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public int inDegree(V vertex) {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+//	@Override
+	public boolean isDest(V vertex, E edge) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public boolean isPredecessor(V v1, V v2) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public boolean isSource(V vertex, E edge) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public boolean isSuccessor(V v1, V v2) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public int outDegree(V vertex) {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+//	@Override
+	public boolean addEdge(E edge, Collection<? extends V> vertices) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public boolean addEdge(E edge, Collection<? extends V> vertices,
+			EdgeType edgeType) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public boolean addVertex(V vertex) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public boolean containsEdge(E edge) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public boolean containsVertex(V vertex) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public int degree(V vertex) {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+//	@Override
+	public E findEdge(V v1, V v2) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public Collection<E> findEdgeSet(V v1, V v2) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public EdgeType getDefaultEdgeType() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public int getEdgeCount() {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+//	@Override
+	public int getEdgeCount(EdgeType edgeType) {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+//	@Override
+	public EdgeType getEdgeType(E edge) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public Collection<E> getEdges() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public Collection<E> getEdges(EdgeType edgeType) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public int getIncidentCount(E edge) {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+//	@Override
+	public Collection<E> getIncidentEdges(V vertex) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public Collection<V> getIncidentVertices(E edge) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public int getNeighborCount(V vertex) {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+//	@Override
+	public Collection<V> getNeighbors(V vertex) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public int getVertexCount() {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+//	@Override
+	public Collection<V> getVertices() {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+//	@Override
+	public boolean isIncident(V vertex, E edge) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public boolean isNeighbor(V v1, V v2) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public boolean removeEdge(E edge) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+//	@Override
+	public boolean removeVertex(V vertex) {
+		// TODO Auto-generated method stub
+		return false;
+	}
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/spatial/FastRenderingGraph.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/spatial/FastRenderingGraph.java
new file mode 100644
index 0000000..64363ca
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/spatial/FastRenderingGraph.java
@@ -0,0 +1,315 @@
+package edu.uci.ics.jung.visualization.spatial;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.EdgeIndexFunction;
+import edu.uci.ics.jung.graph.util.EdgeType;
+import edu.uci.ics.jung.graph.util.Pair;
+import edu.uci.ics.jung.visualization.BasicVisualizationServer;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.decorators.EdgeShape;
+import edu.uci.ics.jung.visualization.decorators.ParallelEdgeShapeTransformer;
+
+/** 
+ * maintains caches of vertices and edges that will be the subset of the
+ * delegate graph's elements that are contained in some Rectangle.
+ * 
+ * @author tanelso
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+public class FastRenderingGraph<V, E> implements Graph<V, E> {
+
+	protected Graph<V,E> graph;
+	protected Set<V> vertices = new HashSet<V>();
+	protected Set<E> edges = new HashSet<E>();
+	protected boolean dirty;
+	protected Set<Rectangle2D> bounds;
+	protected RenderContext<V,E> rc;
+	protected BasicVisualizationServer<V,E> vv;
+	protected Layout<V,E> layout;
+	
+	public FastRenderingGraph(Graph<V,E> graph, Set<Rectangle2D> bounds, BasicVisualizationServer<V,E> vv) {
+		this.graph = graph;
+		this.bounds = bounds;
+		this.vv = vv;
+		this.rc = vv.getRenderContext();
+	}
+	
+	private void cleanUp() {
+		vertices.clear();
+		edges.clear();
+		for(V v : graph.getVertices()) {
+			checkVertex(v);
+		}
+		for(E e : graph.getEdges()) {
+			checkEdge(e);
+		}
+	}
+	
+	private void checkVertex(V v) {
+        // get the shape to be rendered
+        Shape shape = rc.getVertexShapeTransformer().apply(v);
+        Point2D p = layout.apply(v);
+        p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p);
+        float x = (float)p.getX();
+        float y = (float)p.getY();
+        // create a transform that translates to the location of
+        // the vertex to be rendered
+        AffineTransform xform = AffineTransform.getTranslateInstance(x,y);
+        // transform the vertex shape with xtransform
+        shape = xform.createTransformedShape(shape);
+        for(Rectangle2D r : bounds) {
+        	if(shape.intersects(r)) {
+        		vertices.add(v);
+        	}
+        }
+	}
+	
+	private void checkEdge(E e) {
+        Pair<V> endpoints = graph.getEndpoints(e);
+        V v1 = endpoints.getFirst();
+        V v2 = endpoints.getSecond();
+        
+        Point2D p1 = layout.apply(v1);
+        Point2D p2 = layout.apply(v2);
+        p1 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p1);
+        p2 = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p2);
+        float x1 = (float) p1.getX();
+        float y1 = (float) p1.getY();
+        float x2 = (float) p2.getX();
+        float y2 = (float) p2.getY();
+        
+        boolean isLoop = v1.equals(v2);
+        Shape s2 = rc.getVertexShapeTransformer().apply(v2);
+        Shape edgeShape = rc.getEdgeShapeTransformer().apply(e);
+
+        AffineTransform xform = AffineTransform.getTranslateInstance(x1, y1);
+        
+        if(isLoop) {
+            // this is a self-loop. scale it is larger than the vertex
+            // it decorates and translate it so that its nadir is
+            // at the center of the vertex.
+            Rectangle2D s2Bounds = s2.getBounds2D();
+            xform.scale(s2Bounds.getWidth(),s2Bounds.getHeight());
+            xform.translate(0, -edgeShape.getBounds2D().getWidth()/2);
+        } else if(rc.getEdgeShapeTransformer() instanceof EdgeShape.Orthogonal) {
+            float dx = x2-x1;
+            float dy = y2-y1;
+            int index = 0;
+            if(rc.getEdgeShapeTransformer() instanceof ParallelEdgeShapeTransformer) {
+            	@SuppressWarnings("unchecked")
+				EdgeIndexFunction<V,E> peif = 
+            		((ParallelEdgeShapeTransformer<V,E>)rc.getEdgeShapeTransformer())
+            			.getEdgeIndexFunction();
+            	index = peif.getIndex(null, e);
+            	index *= 20;
+            }
+            GeneralPath gp = new GeneralPath();
+            gp.moveTo(0,0);// the xform will do the translation to x1,y1
+            if(x1 > x2) {
+            	if(y1 > y2) {
+            		gp.lineTo(0, index);
+            		gp.lineTo(dx-index, index);
+            		gp.lineTo(dx-index, dy);
+            		gp.lineTo(dx, dy);
+            	} else {
+            		gp.lineTo(0, -index);
+            		gp.lineTo(dx-index, -index);
+            		gp.lineTo(dx-index, dy);
+            		gp.lineTo(dx, dy);
+            	}
+
+            } else {
+            	if(y1 > y2) {
+            		gp.lineTo(0, index);
+            		gp.lineTo(dx+index, index);
+            		gp.lineTo(dx+index, dy);
+            		gp.lineTo(dx, dy);
+            		
+            	} else {
+            		gp.lineTo(0, -index);
+            		gp.lineTo(dx+index, -index);
+            		gp.lineTo(dx+index, dy);
+            		gp.lineTo(dx, dy);
+            		
+            	}
+            	
+            }
+
+            edgeShape = gp;
+        	
+        } else {
+            // this is a normal edge. Rotate it to the angle between
+            // vertex endpoints, then scale it to the distance between
+            // the vertices
+            float dx = x2-x1;
+            float dy = y2-y1;
+            float thetaRadians = (float) Math.atan2(dy, dx);
+            xform.rotate(thetaRadians);
+            float dist = (float) Math.sqrt(dx*dx + dy*dy);
+            xform.scale(dist, 1.0);
+        }
+        
+        edgeShape = xform.createTransformedShape(edgeShape);
+        for(Rectangle2D r : bounds) {
+        	if(edgeShape.intersects(r)) {
+        		edges.add(e);
+        	}
+        }
+	}
+
+	public Set<Rectangle2D> getBounds() {
+		return bounds;
+	}
+
+	public void setBounds(Set<Rectangle2D> bounds) {
+		this.bounds = bounds;
+	}
+
+	public boolean addEdge(E edge, Collection<? extends V> vertices,
+			EdgeType edgeType) {
+		return graph.addEdge(edge, vertices, edgeType);
+	}
+	public boolean addEdge(E edge, Collection<? extends V> vertices) {
+		return graph.addEdge(edge, vertices);
+	}
+	public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) {
+		return graph.addEdge(e, v1, v2, edgeType);
+	}
+	public boolean addEdge(E e, V v1, V v2) {
+		return graph.addEdge(e, v1, v2);
+	}
+	public boolean addVertex(V vertex) {
+		return graph.addVertex(vertex);
+	}
+	public boolean containsEdge(E edge) {
+		return graph.containsEdge(edge);
+	}
+	public boolean containsVertex(V vertex) {
+		return graph.containsVertex(vertex);
+	}
+	public int degree(V vertex) {
+		return graph.degree(vertex);
+	}
+	public E findEdge(V v1, V v2) {
+		return graph.findEdge(v1, v2);
+	}
+	public Collection<E> findEdgeSet(V v1, V v2) {
+		return graph.findEdgeSet(v1, v2);
+	}
+	public EdgeType getDefaultEdgeType() {
+		return graph.getDefaultEdgeType();
+	}
+	public V getDest(E directedEdge) {
+		return graph.getDest(directedEdge);
+	}
+	public int getEdgeCount() {
+		return graph.getEdgeCount();
+	}
+	public int getEdgeCount(EdgeType edgeType) {
+		return graph.getEdgeCount(edgeType);
+	}
+	public Collection<E> getEdges() {
+		if(dirty) {
+			cleanUp();
+		}
+		return edges;
+	}
+	public Collection<E> getEdges(EdgeType edgeType) {
+		return graph.getEdges(edgeType);
+	}
+	public EdgeType getEdgeType(E edge) {
+		return graph.getEdgeType(edge);
+	}
+	public Pair<V> getEndpoints(E edge) {
+		return graph.getEndpoints(edge);
+	}
+	public int getIncidentCount(E edge) {
+		return graph.getIncidentCount(edge);
+	}
+	public Collection<E> getIncidentEdges(V vertex) {
+		return graph.getIncidentEdges(vertex);
+	}
+	public Collection<V> getIncidentVertices(E edge) {
+		return graph.getIncidentVertices(edge);
+	}
+	public Collection<E> getInEdges(V vertex) {
+		return graph.getInEdges(vertex);
+	}
+	public int getNeighborCount(V vertex) {
+		return graph.getNeighborCount(vertex);
+	}
+	public Collection<V> getNeighbors(V vertex) {
+		return graph.getNeighbors(vertex);
+	}
+	public V getOpposite(V vertex, E edge) {
+		return graph.getOpposite(vertex, edge);
+	}
+	public Collection<E> getOutEdges(V vertex) {
+		return graph.getOutEdges(vertex);
+	}
+	public int getPredecessorCount(V vertex) {
+		return graph.getPredecessorCount(vertex);
+	}
+	public Collection<V> getPredecessors(V vertex) {
+		return graph.getPredecessors(vertex);
+	}
+	public V getSource(E directedEdge) {
+		return graph.getSource(directedEdge);
+	}
+	public int getSuccessorCount(V vertex) {
+		return graph.getSuccessorCount(vertex);
+	}
+	public Collection<V> getSuccessors(V vertex) {
+		return graph.getSuccessors(vertex);
+	}
+	public int getVertexCount() {
+		return graph.getVertexCount();
+	}
+	public Collection<V> getVertices() {
+		if(dirty) cleanUp();
+		return vertices;
+	}
+	public int inDegree(V vertex) {
+		return graph.inDegree(vertex);
+	}
+	public boolean isDest(V vertex, E edge) {
+		return graph.isDest(vertex, edge);
+	}
+	public boolean isIncident(V vertex, E edge) {
+		return graph.isIncident(vertex, edge);
+	}
+	public boolean isNeighbor(V v1, V v2) {
+		return graph.isNeighbor(v1, v2);
+	}
+	public boolean isPredecessor(V v1, V v2) {
+		return graph.isPredecessor(v1, v2);
+	}
+	public boolean isSource(V vertex, E edge) {
+		return graph.isSource(vertex, edge);
+	}
+	public boolean isSuccessor(V v1, V v2) {
+		return graph.isSuccessor(v1, v2);
+	}
+	public int outDegree(V vertex) {
+		return graph.outDegree(vertex);
+	}
+	public boolean removeEdge(E edge) {
+		return graph.removeEdge(edge);
+	}
+	public boolean removeVertex(V vertex) {
+		return graph.removeVertex(vertex);
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/spatial/FastRenderingLayout.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/spatial/FastRenderingLayout.java
new file mode 100644
index 0000000..c5d6550
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/spatial/FastRenderingLayout.java
@@ -0,0 +1,74 @@
+package edu.uci.ics.jung.visualization.spatial;
+
+import java.awt.Dimension;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Graph;
+
+/**
+ * break into several rectangular areas, each of which will have a reference Graph
+ * @author tanelso
+ *
+ * @param <V> the vertex type
+ * @param <E> the edge type
+ */
+public class FastRenderingLayout<V,E> implements Layout<V,E> {
+	
+	protected Layout<V,E> layout;
+	protected Graph<V,E> graph;
+	
+	protected Rectangle2D[][] grid;
+	
+	public FastRenderingLayout(Layout<V,E> layout) {
+		this.layout = layout;
+	}
+
+	public Graph<V, E> getGraph() {
+		return layout.getGraph();
+	}
+
+	public Dimension getSize() {
+		return layout.getSize();
+	}
+
+	public void initialize() {
+		layout.initialize();
+	}
+
+	public boolean isLocked(V v) {
+		return layout.isLocked(v);
+	}
+
+	public void lock(V v, boolean state) {
+		layout.lock(v, state);
+	}
+
+	public void reset() {
+		layout.reset();
+	}
+
+	public void setGraph(Graph<V, E> graph) {
+//		layout.setGraph(new FastRenderingGraph(graph));
+	}
+
+	public void setInitializer(Function<V, Point2D> initializer) {
+		layout.setInitializer(initializer);
+	}
+
+	public void setLocation(V v, Point2D location) {
+		layout.setLocation(v, location);
+	}
+
+	public void setSize(Dimension d) {
+		layout.setSize(d);
+	}
+
+	public Point2D apply(V arg0) {
+		return layout.apply(arg0);
+	}
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/subLayout/GraphCollapser.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/subLayout/GraphCollapser.java
new file mode 100644
index 0000000..d3d8a91
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/subLayout/GraphCollapser.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+package edu.uci.ics.jung.visualization.subLayout;
+
+import com.google.common.base.Preconditions;
+
+import java.util.Collection;
+import java.util.logging.Logger;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.Pair;
+
+ at SuppressWarnings({"rawtypes", "unchecked"})
+public class GraphCollapser  {
+    
+    private static final Logger logger = Logger.getLogger(GraphCollapser.class.getClass().getName());
+	private Graph originalGraph;
+    
+    public GraphCollapser(Graph originalGraph) {
+        this.originalGraph = originalGraph;
+    }
+    
+    Graph createGraph() throws InstantiationException, IllegalAccessException {
+        return (Graph)originalGraph.getClass().newInstance();
+    }
+    
+    public Graph collapse(Graph inGraph, Graph clusterGraph) {
+        
+        if(clusterGraph.getVertexCount() < 2) return inGraph;
+
+        Graph graph = inGraph;
+        try {
+            graph = createGraph();
+        } catch(Exception ex) {
+            ex.printStackTrace();
+        }
+        Collection cluster = clusterGraph.getVertices();
+        
+        // add all vertices in the delegate, unless the vertex is in the
+        // cluster.
+        for(Object v : inGraph.getVertices()) {
+            if(cluster.contains(v) == false) {
+                graph.addVertex(v);
+            }
+        }
+        // add the clusterGraph as a vertex
+        graph.addVertex(clusterGraph);
+        
+        //add all edges from the inGraph, unless both endpoints of
+        // the edge are in the cluster
+        for(Object e : (Collection<?>)inGraph.getEdges()) {
+            Pair endpoints = inGraph.getEndpoints(e);
+            // don't add edges whose endpoints are both in the cluster
+            if(cluster.containsAll(endpoints) == false) {
+
+                if(cluster.contains(endpoints.getFirst())) {
+                	graph.addEdge(e, clusterGraph, endpoints.getSecond(), inGraph.getEdgeType(e));
+
+                } else if(cluster.contains(endpoints.getSecond())) {
+                	graph.addEdge(e, endpoints.getFirst(), clusterGraph, inGraph.getEdgeType(e));
+
+                } else {
+                	graph.addEdge(e,endpoints.getFirst(), endpoints.getSecond(), inGraph.getEdgeType(e));
+                }
+            }
+        }
+        return graph;
+    }
+    
+    public Graph expand(Graph inGraph, Graph clusterGraph) {
+        Graph graph = inGraph;
+        try {
+            graph = createGraph();
+        } catch(Exception ex) {
+            ex.printStackTrace();
+        }
+        Collection cluster = clusterGraph.getVertices();
+        logger.fine("cluster to expand is "+cluster);
+
+        // put all clusterGraph vertices and edges into the new Graph
+        for(Object v : cluster) {
+            graph.addVertex(v);
+            for(Object edge : clusterGraph.getIncidentEdges(v)) {
+                Pair endpoints = clusterGraph.getEndpoints(edge);
+                graph.addEdge(edge, endpoints.getFirst(), endpoints.getSecond(), clusterGraph.getEdgeType(edge));
+            }
+        }
+        // add all the vertices from the current graph except for
+        // the cluster we are expanding
+        for(Object v : inGraph.getVertices()) {
+            if(v.equals(clusterGraph) == false) {
+                graph.addVertex(v);
+            }
+        }
+
+        // now that all vertices have been added, add the edges,
+        // ensuring that no edge contains a vertex that has not
+        // already been added
+        for(Object v : inGraph.getVertices()) {
+            if(v.equals(clusterGraph) == false) {
+                for(Object edge : inGraph.getIncidentEdges(v)) {
+                    Pair endpoints = inGraph.getEndpoints(edge);
+                    Object v1 = endpoints.getFirst();
+                    Object v2 = endpoints.getSecond();
+                     if(cluster.containsAll(endpoints) == false) {
+                        if(clusterGraph.equals(v1)) {
+                            // i need a new v1
+                            Object originalV1 = originalGraph.getEndpoints(edge).getFirst();
+                            Object newV1 = findVertex(graph, originalV1);
+                            Preconditions.checkState(newV1 != null, "newV1 for "+originalV1+" was not found!");
+                            graph.addEdge(edge, newV1, v2, inGraph.getEdgeType(edge));
+                        } else if(clusterGraph.equals(v2)) {
+                            // i need a new v2
+                            Object originalV2 = originalGraph.getEndpoints(edge).getSecond();
+                            Object newV2 = findVertex(graph, originalV2);
+                            Preconditions.checkState(newV2 != null, "newV2 for "+originalV2+" was not found!");
+                            graph.addEdge(edge, v1, newV2, inGraph.getEdgeType(edge));
+                        } else {
+                        	graph.addEdge(edge, v1, v2, inGraph.getEdgeType(edge));
+                        }
+                    }
+                }
+            }
+        }
+        return graph;
+    }
+    Object findVertex(Graph inGraph, Object vertex) {
+        Collection vertices = inGraph.getVertices();
+        if(vertices.contains(vertex)) {
+            return vertex;
+        }
+        for(Object v : vertices) {
+            if(v instanceof Graph) {
+                Graph g = (Graph)v;
+                if(contains(g, vertex)) {
+                    return v;
+                }
+            }
+        }
+        return null;
+    }
+    
+    private boolean contains(Graph inGraph, Object vertex) {
+    	boolean contained = false;
+    	if(inGraph.getVertices().contains(vertex)) return true;
+    	for(Object v : inGraph.getVertices()) {
+    		if(v instanceof Graph) {
+    			contained |= contains((Graph)v, vertex);
+    		}
+    	}
+    	return contained;
+    }
+    
+    public Graph getClusterGraph(Graph inGraph, Collection picked) {
+        Graph clusterGraph;
+        try {
+            clusterGraph = createGraph();
+        } catch (InstantiationException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            return null;
+        } catch (IllegalAccessException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            return null;
+        }
+        for(Object v : picked) {
+        	clusterGraph.addVertex(v);
+            Collection edges = inGraph.getIncidentEdges(v);
+            for(Object edge : edges) {
+                Pair endpoints = inGraph.getEndpoints(edge);
+                Object v1 = endpoints.getFirst();
+                Object v2 = endpoints.getSecond();
+                if(picked.containsAll(endpoints)) {
+                    clusterGraph.addEdge(edge, v1, v2, inGraph.getEdgeType(edge));
+                }
+            }
+        }
+        return clusterGraph;
+    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/subLayout/TreeCollapser.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/subLayout/TreeCollapser.java
new file mode 100644
index 0000000..1f6d4a2
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/subLayout/TreeCollapser.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 23, 2005
+ */
+package edu.uci.ics.jung.visualization.subLayout;
+
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.graph.Forest;
+import edu.uci.ics.jung.graph.util.TreeUtils;
+
+public class TreeCollapser  {
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+	public void collapse(Layout layout, Forest tree, Object subRoot) throws InstantiationException, IllegalAccessException {
+        
+    	// get a sub tree from subRoot
+    	Forest subTree = TreeUtils.getSubTree(tree, subRoot);
+    	Object parent = null;
+    	Object edge = null;
+    	if(tree.getPredecessorCount(subRoot) > 0) {
+    		parent = tree.getPredecessors(subRoot).iterator().next();
+    		edge = tree.getInEdges(subRoot).iterator().next();
+    	}	
+    	tree.removeVertex(subRoot);
+    	if(parent != null) {
+    		tree.addEdge(edge, parent, subTree);
+    	} else {
+    		tree.addVertex(subTree);
+    	}
+    	
+    	layout.setLocation(subTree, (Point2D)layout.apply(subRoot));
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+	public void expand(Forest tree, Forest subTree) {
+
+    	Object parent = null;
+    	Object edge = null;
+    	if(tree.getPredecessorCount(subTree) > 0) {
+    		parent = tree.getPredecessors(subTree).iterator().next();
+    		edge = tree.getInEdges(subTree).iterator().next();
+    	}
+    	tree.removeVertex(subTree);
+    	TreeUtils.addSubTree(tree, subTree, parent, edge);
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/subLayout/package.html b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/subLayout/package.html
new file mode 100644
index 0000000..53350f1
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/subLayout/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Visualization mechanisms relating to grouping or hiding specified element sets.
+
+</body>
+</html>
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/AbstractLensSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/AbstractLensSupport.java
new file mode 100644
index 0000000..cb6ff63
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/AbstractLensSupport.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 21, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.geom.RectangularShape;
+
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.VisualizationServer.Paintable;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+/**
+ * A class to make it easy to add an
+ * examining lens to a jung graph application. See HyperbolicTransformerDemo,
+ * ViewLensSupport and LayoutLensSupport
+ * for examples of how to use it.
+ * 
+ * @author Tom Nelson
+ */
+public abstract class AbstractLensSupport<V,E> implements LensSupport {
+
+    protected VisualizationViewer<V,E> vv;
+    protected VisualizationViewer.GraphMouse graphMouse;
+    protected LensTransformer lensTransformer;
+    protected ModalGraphMouse lensGraphMouse;
+    protected Lens lens;
+    protected LensControls lensControls;
+    protected String defaultToolTipText;
+
+    protected static final String instructions = 
+        "<html><center>Mouse-Drag the Lens center to move it<p>"+
+        "Mouse-Drag the Lens edge to resize it<p>"+
+        "Ctrl+MouseWheel to change magnification</center></html>";
+    
+    /**
+     * create the base class, setting common members and creating
+     * a custom GraphMouse
+     * @param vv the VisualizationViewer to work on
+     * @param lensGraphMouse the GraphMouse instance to use for the lens
+     */
+    public AbstractLensSupport(VisualizationViewer<V,E> vv, ModalGraphMouse lensGraphMouse) {
+        this.vv = vv;
+        this.graphMouse = vv.getGraphMouse();
+        this.defaultToolTipText = vv.getToolTipText();
+        this.lensGraphMouse = lensGraphMouse;
+    }
+
+    public void activate(boolean state) {
+        if(state) activate();
+        else deactivate();
+    }
+    
+    public LensTransformer getLensTransformer() {
+        return lensTransformer;
+    }
+
+    /**
+     * @return the hyperbolicGraphMouse.
+     */
+    public ModalGraphMouse getGraphMouse() {
+        return lensGraphMouse;
+    }
+
+    /**
+     * the background for the hyperbolic projection
+     * @author Tom Nelson 
+     */
+    public static class Lens implements Paintable {
+        LensTransformer lensTransformer;
+        RectangularShape lensShape;
+        Paint paint = Color.decode("0xdddddd");
+        
+        public Lens(LensTransformer lensTransformer) {
+            this.lensTransformer = lensTransformer;
+            this.lensShape = lensTransformer.getLensShape();
+        }
+        
+        /**
+		 * @return the paint
+		 */
+		public Paint getPaint() {
+			return paint;
+		}
+
+		/**
+		 * @param paint the paint to set
+		 */
+		public void setPaint(Paint paint) {
+			this.paint = paint;
+		}
+
+        public void paint(Graphics g) {
+            Graphics2D g2d = (Graphics2D)g;
+            g2d.setPaint(paint);
+            g2d.fill(lensShape);
+        }
+
+        public boolean useTransform() {
+            return true;
+        }
+    }
+    
+    /**
+     * the background for the hyperbolic projection
+     * @author Tom Nelson 
+     *
+     *
+     */
+    public static class LensControls  implements Paintable {
+        LensTransformer lensTransformer;
+        RectangularShape lensShape;
+        Paint paint = Color.gray;
+        
+        public LensControls(LensTransformer lensTransformer) {
+            this.lensTransformer = lensTransformer;
+            this.lensShape = lensTransformer.getLensShape();
+        }
+        
+        /**
+		 * @return the paint
+		 */
+		public Paint getPaint() {
+			return paint;
+		}
+
+		/**
+		 * @param paint the paint to set
+		 */
+		public void setPaint(Paint paint) {
+			this.paint = paint;
+		}
+
+        public void paint(Graphics g) {
+            
+            Graphics2D g2d = (Graphics2D)g;
+            g2d.setPaint(paint);
+            g2d.draw(lensShape);
+            int centerX = (int)Math.round(lensShape.getCenterX());
+            int centerY = (int)Math.round(lensShape.getCenterY());
+            g.drawOval(centerX-10, centerY-10, 20, 20);
+        }
+
+        public boolean useTransform() {
+            return true;
+        }
+    }
+
+	/**
+	 * @return the lens
+	 */
+	public Lens getLens() {
+		return lens;
+	}
+
+	/**
+	 * @param lens the lens to set
+	 */
+	public void setLens(Lens lens) {
+		this.lens = lens;
+	}
+
+	/**
+	 * @return the lensControls
+	 */
+	public LensControls getLensControls() {
+		return lensControls;
+	}
+
+	/**
+	 * @param lensControls the lensControls to set
+	 */
+	public void setLensControls(LensControls lensControls) {
+		this.lensControls = lensControls;
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/AffineTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/AffineTransformer.java
new file mode 100644
index 0000000..77d52d4
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/AffineTransformer.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Apr 16, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer;
+
+/**
+ *
+ * Provides methods to map points from one coordinate system to
+ * another, by delegating to a wrapped AffineTransform (uniform)
+ * and its inverse.
+ * 
+ * @author Tom Nelson
+ */
+public class AffineTransformer implements BidirectionalTransformer, ShapeTransformer {
+
+    protected AffineTransform inverse;
+
+    /**
+     * The AffineTransform to use; initialized to identity.
+     * 
+     */
+    protected AffineTransform transform = new AffineTransform();
+    
+    /**
+     * Create an instance that does not transform points.
+     */
+    public AffineTransformer() {
+        // nothing left to do
+    }
+
+    /**
+     * Create an instance with the supplied transform.
+     * @param transform the transform to use
+     */
+    public AffineTransformer(AffineTransform transform) {
+        if(transform != null) 
+            this.transform = transform;
+    }
+
+    /**
+     * @return Returns the transform.
+     */
+    public AffineTransform getTransform() {
+        return transform;
+    }
+    /**
+     * @param transform The transform to set.
+     */
+    public void setTransform(AffineTransform transform) {
+        this.transform = transform;
+    }
+    
+    /**
+     * applies the inverse transform to the supplied point
+     * @param p the point to transform
+     * @return the transformed point
+     */
+    public Point2D inverseTransform(Point2D p) {
+
+        return getInverse().transform(p, null);
+    }
+    
+    public AffineTransform getInverse() {
+        if(inverse == null) {
+            try {
+                inverse = transform.createInverse();
+            } catch (NoninvertibleTransformException e) {
+                e.printStackTrace();
+            }
+        }
+        return inverse;
+    }
+    
+    /**
+     * @return the transform's x scale value
+     */
+    public double getScaleX() {
+        return transform.getScaleX();   
+    }
+    
+    /**
+     * @return the transform's y scale value
+     */
+    public double getScaleY() {
+        return transform.getScaleY();
+    }
+
+    /**
+     * @return the transform's overall scale magnitude
+     */
+    public double getScale() {
+    		return Math.sqrt(transform.getDeterminant());
+    }
+    
+    /**
+     * @return the transform's x shear value
+     */
+    public double getShearX() {
+        return transform.getShearX();
+    }
+    
+    /**
+     * @return the transform's y shear value
+     */
+    public double getShearY() {
+        return transform.getShearY();
+    }
+    
+    /**
+     * @return the transform's x translate value
+     */
+    public double getTranslateX() {
+        return transform.getTranslateX();
+    }
+    
+    /**
+     * @return the transform's y translate value
+     */
+    public double getTranslateY() {
+        return transform.getTranslateY();
+    }
+    
+    /**
+     * Applies the transform to the supplied point.
+     * 
+     * @param p the point to be transformed
+     * @return the transformed point
+     */
+    public Point2D transform(Point2D p) {
+        if(p == null) return null;
+        return transform.transform(p, null);
+    }
+    
+    /**
+     * Transform the supplied shape from graph (layout) to screen (view) coordinates.
+     * 
+     * @return the GeneralPath of the transformed shape
+     */
+    public Shape transform(Shape shape) {
+        GeneralPath newPath = new GeneralPath();
+        float[] coords = new float[6];
+        for(PathIterator iterator=shape.getPathIterator(null);
+            iterator.isDone() == false;
+            iterator.next()) {
+            int type = iterator.currentSegment(coords);
+            switch(type) {
+            case PathIterator.SEG_MOVETO:
+                Point2D p = transform(new Point2D.Float(coords[0], coords[1]));
+                newPath.moveTo((float)p.getX(), (float)p.getY());
+                break;
+                
+            case PathIterator.SEG_LINETO:
+                p = transform(new Point2D.Float(coords[0], coords[1]));
+                newPath.lineTo((float)p.getX(), (float) p.getY());
+                break;
+                
+            case PathIterator.SEG_QUADTO:
+                p = transform(new Point2D.Float(coords[0], coords[1]));
+                Point2D q = transform(new Point2D.Float(coords[2], coords[3]));
+                newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY());
+                break;
+                
+            case PathIterator.SEG_CUBICTO:
+                p = transform(new Point2D.Float(coords[0], coords[1]));
+                q = transform(new Point2D.Float(coords[2], coords[3]));
+                Point2D r = transform(new Point2D.Float(coords[4], coords[5]));
+                newPath.curveTo((float)p.getX(), (float)p.getY(), 
+                        (float)q.getX(), (float)q.getY(),
+                        (float)r.getX(), (float)r.getY());
+                break;
+                
+            case PathIterator.SEG_CLOSE:
+                newPath.closePath();
+                break;
+                    
+            }
+        }
+        return newPath;
+    }
+
+    /**
+     * Transform the supplied shape from screen (view) to graph (layout) coordinates.
+     * 
+     * @return the GeneralPath of the transformed shape
+     */
+    public Shape inverseTransform(Shape shape) {
+        GeneralPath newPath = new GeneralPath();
+        float[] coords = new float[6];
+        for(PathIterator iterator=shape.getPathIterator(null);
+            iterator.isDone() == false;
+            iterator.next()) {
+            int type = iterator.currentSegment(coords);
+            switch(type) {
+            case PathIterator.SEG_MOVETO:
+                Point2D p = inverseTransform(new Point2D.Float(coords[0], coords[1]));
+                newPath.moveTo((float)p.getX(), (float)p.getY());
+                break;
+                
+            case PathIterator.SEG_LINETO:
+                p = inverseTransform(new Point2D.Float(coords[0], coords[1]));
+                newPath.lineTo((float)p.getX(), (float) p.getY());
+                break;
+                
+            case PathIterator.SEG_QUADTO:
+                p = inverseTransform(new Point2D.Float(coords[0], coords[1]));
+                Point2D q = inverseTransform(new Point2D.Float(coords[2], coords[3]));
+                newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY());
+                break;
+                
+            case PathIterator.SEG_CUBICTO:
+                p = inverseTransform(new Point2D.Float(coords[0], coords[1]));
+                q = inverseTransform(new Point2D.Float(coords[2], coords[3]));
+                Point2D r = inverseTransform(new Point2D.Float(coords[4], coords[5]));
+                newPath.curveTo((float)p.getX(), (float)p.getY(), 
+                        (float)q.getX(), (float)q.getY(),
+                        (float)r.getX(), (float)r.getY());
+                break;
+                
+            case PathIterator.SEG_CLOSE:
+                newPath.closePath();
+                break;
+                    
+            }
+        }
+        return newPath;
+    }
+    
+    public double getRotation() {    
+        double[] unitVector = new double[]{0,0,1,0};
+        double[] result = new double[4];
+
+        transform.transform(unitVector, 0, result, 0, 2);
+
+        double dy = Math.abs(result[3] - result[1]);
+        double length = Point2D.distance(result[0], result[1], result[2], result[3]);
+        double rotation = Math.asin(dy / length);        
+        
+        if (result[3] - result[1] > 0) {
+            if (result[2] - result[0] < 0) {
+                rotation = Math.PI - rotation;
+            }
+        } else {
+            if (result[2] - result[0] > 0) {
+                rotation = 2 * Math.PI - rotation;
+            } else {
+                rotation = rotation + Math.PI;
+            }
+        }
+
+        return rotation;
+    }
+
+    @Override
+    public String toString() {
+        return "Transformer using "+transform;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/BidirectionalTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/BidirectionalTransformer.java
new file mode 100644
index 0000000..411add6
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/BidirectionalTransformer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Apr 16, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform;
+
+import java.awt.geom.Point2D;
+
+/**
+ * Provides methods to map points from one coordinate system to
+ * another: graph to screen and screen to graph.
+ * 
+ * @author Tom Nelson 
+ */
+public interface BidirectionalTransformer {
+    
+    /**
+     * convert the supplied graph coordinate to the screen coordinate
+     * @param p graph point to convert
+     * @return screen point
+     */
+    Point2D transform(Point2D p);
+    
+    /**
+     * convert the supplied screen coordinate to the graph coordinate.
+     * @param p screen point to convert
+     * @return the graph point
+     */
+    Point2D inverseTransform(Point2D p);
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/HyperbolicTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/HyperbolicTransformer.java
new file mode 100644
index 0000000..9bed804
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/HyperbolicTransformer.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.visualization.transform;
+
+import java.awt.Component;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.algorithms.layout.PolarPoint;
+
+/**
+ * HyperbolicTransformer wraps a MutableAffineTransformer and modifies
+ * the transform and inverseTransform methods so that they create a
+ * fisheye projection of the graph points, with points near the
+ * center spread out and points near the edges collapsed onto the
+ * circumference of an ellipse.
+ * 
+ * HyperbolicTransformer is not an affine transform, but it uses an
+ * affine transform to cause translation, scaling, rotation, and shearing
+ * while applying a non-affine hyperbolic filter in its transform and
+ * inverseTransform methods.
+ * 
+ * @author Tom Nelson 
+ */
+public class HyperbolicTransformer extends LensTransformer implements MutableTransformer {
+    /**
+     * create an instance, setting values from the passed component
+     * and registering to listen for size changes on the component
+     * @param component the component used for rendering
+     */
+    public HyperbolicTransformer(Component component) {
+        this(component, new MutableAffineTransformer());
+    }
+
+    /**
+     * Create an instance with a possibly shared transform.
+     * 
+     * @param component the component used for rendering
+     * @param delegate the transformer to use
+     */
+    public HyperbolicTransformer(Component component, MutableTransformer delegate) {
+    		super(component, delegate);
+   }
+    
+    /**
+     * override base class transform to project the fisheye effect
+     */
+    public Point2D transform(Point2D graphPoint) {
+        if(graphPoint == null) return null;
+        Point2D viewCenter = getViewCenter();
+        double viewRadius = getViewRadius();
+        double ratio = getRatio();
+        // transform the point from the graph to the view
+        Point2D viewPoint = delegate.transform(graphPoint);
+        // calculate point from center
+        double dx = viewPoint.getX() - viewCenter.getX();
+        double dy = viewPoint.getY() - viewCenter.getY();
+        // factor out ellipse
+        dx *= ratio;
+        Point2D pointFromCenter = new Point2D.Double(dx, dy);
+        
+        PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter);
+        double theta = polar.getTheta();
+        double radius = polar.getRadius();
+        if(radius > viewRadius) return viewPoint;
+        
+        double mag = Math.tan(Math.PI/2*magnification);
+        radius *= mag;
+        
+        radius = Math.min(radius, viewRadius);
+        radius /= viewRadius;
+        radius *= Math.PI/2;
+        radius = Math.abs(Math.atan(radius));
+        radius *= viewRadius;
+        Point2D projectedPoint = PolarPoint.polarToCartesian(theta, radius);
+        projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY());
+        Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(),
+                projectedPoint.getY()+viewCenter.getY());
+        return translatedBack;
+    }
+    
+    /**
+     * override base class to un-project the fisheye effect
+     */
+    public Point2D inverseTransform(Point2D viewPoint) {
+        
+        Point2D viewCenter = getViewCenter();
+        double viewRadius = getViewRadius();
+        double ratio = getRatio();
+        double dx = viewPoint.getX() - viewCenter.getX();
+        double dy = viewPoint.getY() - viewCenter.getY();
+        // factor out ellipse
+        dx *= ratio;
+
+        Point2D pointFromCenter = new Point2D.Double(dx, dy);
+        
+        PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter);
+
+        double radius = polar.getRadius();
+        if(radius > viewRadius) return delegate.inverseTransform(viewPoint);
+        
+        radius /= viewRadius;
+        radius = Math.abs(Math.tan(radius));
+        radius /= Math.PI/2;
+        radius *= viewRadius;
+        double mag = Math.tan(Math.PI/2*magnification);
+        radius /= mag;
+        polar.setRadius(radius);
+        Point2D projectedPoint = PolarPoint.polarToCartesian(polar);
+        projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY());
+        Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(),
+                projectedPoint.getY()+viewCenter.getY());
+        return delegate.inverseTransform(translatedBack);
+    }
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/LayoutLensSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/LayoutLensSupport.java
new file mode 100644
index 0000000..3a4751b
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/LayoutLensSupport.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 21, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform;
+
+import java.awt.Dimension;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalLensGraphMouse;
+import edu.uci.ics.jung.visualization.picking.LayoutLensShapePickSupport;
+/**
+ * A class to make it easy to add an 
+ * examining lens to a jung graph application. See HyperbolicTransformerDemo
+ * for an example of how to use it.
+ * 
+ * @author Tom Nelson
+ *
+ *
+ */
+public class LayoutLensSupport<V,E> extends AbstractLensSupport<V,E> 
+    implements LensSupport {
+
+	protected GraphElementAccessor<V,E> pickSupport;
+	
+    public LayoutLensSupport(VisualizationViewer<V,E> vv) {
+        this(vv, new HyperbolicTransformer(
+        		vv, vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT)),
+             new ModalLensGraphMouse());
+    }
+    
+    /**
+     * Create an instance with the specified parameters.
+     * 
+     * @param vv the visualization viewer used for rendering
+     * @param lensTransformer the lens transformer to use
+     * @param lensGraphMouse the lens input handler
+     */
+    public LayoutLensSupport(VisualizationViewer<V,E> vv, LensTransformer lensTransformer,
+            ModalGraphMouse lensGraphMouse) {
+        super(vv, lensGraphMouse);
+        this.lensTransformer = lensTransformer;
+        this.pickSupport = vv.getPickSupport();
+
+        Dimension d = vv.getSize();
+        if(d.width <= 0 || d.height <= 0) {
+            d = vv.getPreferredSize();
+        }
+        lensTransformer.setViewRadius(d.width/5);
+   }
+    
+    public void activate() {
+        if(lens == null) {
+            lens = new Lens(lensTransformer);
+        }
+        if(lensControls == null) {
+            lensControls = new LensControls(lensTransformer);
+        }
+        vv.getRenderContext().setPickSupport(new LayoutLensShapePickSupport<V,E>(vv));
+        vv.getRenderContext().getMultiLayerTransformer().setTransformer(Layer.LAYOUT, lensTransformer);
+        vv.prependPreRenderPaintable(lens);
+        vv.addPostRenderPaintable(lensControls);
+        vv.setGraphMouse(lensGraphMouse);
+        vv.setToolTipText(instructions);
+        vv.repaint();
+    }
+    
+    public void deactivate() {
+        if(lensTransformer != null) {
+            vv.removePreRenderPaintable(lens);
+            vv.removePostRenderPaintable(lensControls);
+            vv.getRenderContext().getMultiLayerTransformer().setTransformer(Layer.LAYOUT, lensTransformer.getDelegate());
+        }
+        vv.getRenderContext().setPickSupport(pickSupport);
+        vv.setToolTipText(defaultToolTipText);
+        vv.setGraphMouse(graphMouse);
+        vv.repaint();
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/LensSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/LensSupport.java
new file mode 100644
index 0000000..9813940
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/LensSupport.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 5, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform;
+
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+
+/**
+ * basic API for implementing lens projection support
+ * 
+ * @author Tom Nelson 
+ *
+ */
+public interface LensSupport {
+
+    void activate();
+    void deactivate();
+    void activate(boolean state);
+    LensTransformer getLensTransformer();
+    
+    ModalGraphMouse getGraphMouse();
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/LensTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/LensTransformer.java
new file mode 100644
index 0000000..4559b34
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/LensTransformer.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.visualization.transform;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Shape;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RectangularShape;
+
+/**
+ * LensTransformer wraps a MutableAffineTransformer and modifies
+ * the transform and inverseTransform methods so that they create a
+ * projection of the graph points within an elliptical lens.
+ * 
+ * LensTransformer uses an
+ * affine transform to cause translation, scaling, rotation, and shearing
+ * while applying a possibly non-affine filter in its transform and
+ * inverseTransform methods.
+ * 
+ * @author Tom Nelson
+ */
+public abstract class LensTransformer extends MutableTransformerDecorator implements MutableTransformer {
+
+    /**
+     * the area affected by the transform
+     */
+    protected RectangularShape lensShape = new Ellipse2D.Float();
+    
+    protected float magnification = 0.7f;
+    
+    /**
+     * Create an instance with a possibly shared transform.
+     * 
+     * @param component the component used for rendering
+     * @param delegate the transformer to use
+     */
+    public LensTransformer(Component component, MutableTransformer delegate) {
+    		super(delegate);
+        setComponent(component);
+        component.addComponentListener(new ComponentListenerImpl());
+   }
+    
+    /**
+     * Set values from the passed component.
+     * @param component the component used for rendering
+     */
+    private void setComponent(Component component) {
+        Dimension d = component.getSize();
+        if(d.width <= 0 || d.height <= 0) {
+            d = component.getPreferredSize();
+        }
+        float ewidth = d.width/1.5f;
+        float eheight = d.height/1.5f;
+        lensShape.setFrame(d.width/2-ewidth/2, d.height/2-eheight/2, ewidth, eheight);
+    }
+    
+    public float getMagnification() {
+        return magnification;
+    }
+
+    public void setMagnification(float magnification) {
+        this.magnification = magnification;
+    }
+    
+    public Point2D getViewCenter() {
+        return new Point2D.Double(lensShape.getCenterX(), lensShape.getCenterY());
+    }
+
+    public void setViewCenter(Point2D viewCenter) {
+        double width = lensShape.getWidth();
+        double height = lensShape.getHeight();
+        lensShape.setFrame(viewCenter.getX()-width/2,
+                viewCenter.getY()-height/2,
+                width, height);
+    }
+
+    public double getViewRadius() {
+        return lensShape.getHeight()/2;
+    }
+
+    public void setViewRadius(double viewRadius) {
+        double x = lensShape.getCenterX();
+        double y = lensShape.getCenterY();
+        double viewRatio = getRatio();
+        lensShape.setFrame(x-viewRadius/viewRatio,
+                y-viewRadius,
+                2*viewRadius/viewRatio,
+                2*viewRadius);
+    }
+    
+    /**
+     * @return the ratio between the lens height and lens width
+     */
+    public double getRatio() {
+        return lensShape.getHeight()/lensShape.getWidth();
+    }
+    
+    public void setLensShape(RectangularShape ellipse) {
+        this.lensShape = ellipse;
+    }
+    public RectangularShape getLensShape() {
+        return lensShape;
+    }
+    public void setToIdentity() {
+        this.delegate.setToIdentity();
+    }
+
+    /**
+     * react to size changes on a component
+     */
+    protected class ComponentListenerImpl extends ComponentAdapter {
+        public void componentResized(ComponentEvent e) {
+            setComponent(e.getComponent());
+         }
+    }
+    
+    /**
+     * override base class transform to project the fisheye effect
+     */
+    public abstract Point2D transform(Point2D graphPoint);
+    
+    /**
+     * override base class to un-project the fisheye effect
+     */
+    public abstract Point2D inverseTransform(Point2D viewPoint);
+    
+    public double getDistanceFromCenter(Point2D p) {
+    	
+        double dx = lensShape.getCenterX()-p.getX();
+        double dy = lensShape.getCenterY()-p.getY();
+        dx *= getRatio();
+        return Math.sqrt(dx*dx + dy*dy);
+    }
+    
+    /**
+     * return the supplied shape, translated to the coordinates
+     * that result from calling transform on its center
+     */
+    public Shape transform(Shape shape) {
+    	Rectangle2D bounds = shape.getBounds2D();
+    	Point2D center = new Point2D.Double(bounds.getCenterX(),bounds.getCenterY());
+    	Point2D newCenter = transform(center);
+    	double dx = newCenter.getX()-center.getX();
+    	double dy = newCenter.getY()-center.getY();
+    	AffineTransform at = AffineTransform.getTranslateInstance(dx,dy);
+    	return at.createTransformedShape(shape);
+    }
+    
+    /**
+     * Returns the supplied shape, translated to the coordinates
+     * that result from calling inverseTransform on its center.
+     */
+    public Shape inverseTransform(Shape shape) {
+    	Rectangle2D bounds = shape.getBounds2D();
+    	Point2D center = new Point2D.Double(bounds.getCenterX(),bounds.getCenterY());
+    	Point2D newCenter = inverseTransform(center);
+    	double dx = newCenter.getX()-center.getX();
+    	double dy = newCenter.getY()-center.getY();
+    	AffineTransform at = AffineTransform.getTranslateInstance(dx,dy);
+    	return at.createTransformedShape(shape);
+    }
+
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/MagnifyTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/MagnifyTransformer.java
new file mode 100644
index 0000000..9fa1c05
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/MagnifyTransformer.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.visualization.transform;
+
+import java.awt.Component;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.algorithms.layout.PolarPoint;
+
+/**
+ * MagnifyTransformer wraps a MutableAffineTransformer and modifies
+ * the transform and inverseTransform methods so that they create an
+ * enlarging projection of the graph points.
+ * 
+ * MagnifyTransformer uses an
+ * affine transform to cause translation, scaling, rotation, and shearing
+ * while applying a separate magnification filter in its transform and
+ * inverseTransform methods.
+ * 
+ * @author Tom Nelson 
+ */
+public class MagnifyTransformer extends LensTransformer implements MutableTransformer {
+    /**
+     * Create an instance, setting values from the passed component
+     * and registering to listen for size changes on the component.
+     * 
+     * @param component the component used for rendering
+     */
+    public MagnifyTransformer(Component component) {
+        this(component, new MutableAffineTransformer());
+    }
+    
+    /**
+     * Create an instance with a possibly shared transform.
+     * 
+     * @param component the component used for rendering
+     * @param delegate the transformer to use
+     */
+    public MagnifyTransformer(Component component, MutableTransformer delegate) {
+    		super(component, delegate);
+        this.magnification = 3.f;
+   }
+    
+    /**
+     * override base class transform to project the fisheye effect
+     */
+    public Point2D transform(Point2D graphPoint) {
+        if(graphPoint == null) return null;
+        Point2D viewCenter = getViewCenter();
+        double viewRadius = getViewRadius();
+        double ratio = getRatio();
+        // transform the point from the graph to the view
+        Point2D viewPoint = delegate.transform(graphPoint);
+        // calculate point from center
+        double dx = viewPoint.getX() - viewCenter.getX();
+        double dy = viewPoint.getY() - viewCenter.getY();
+        // factor out ellipse
+        dx *= ratio;
+        Point2D pointFromCenter = new Point2D.Double(dx, dy);
+        
+        PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter);
+        double theta = polar.getTheta();
+        double radius = polar.getRadius();
+        if(radius > viewRadius) return viewPoint;
+        
+        double mag = magnification;
+        radius *= mag;
+        
+        radius = Math.min(radius, viewRadius);
+        Point2D projectedPoint = PolarPoint.polarToCartesian(theta, radius);
+        projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY());
+        Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(),
+                projectedPoint.getY()+viewCenter.getY());
+        return translatedBack;
+    }
+    
+    /**
+     * override base class to un-project the fisheye effect
+     */
+    public Point2D inverseTransform(Point2D viewPoint) {
+        
+        Point2D viewCenter = getViewCenter();
+        double viewRadius = getViewRadius();
+        double ratio = getRatio();
+        double dx = viewPoint.getX() - viewCenter.getX();
+        double dy = viewPoint.getY() - viewCenter.getY();
+        // factor out ellipse
+        dx *= ratio;
+
+        Point2D pointFromCenter = new Point2D.Double(dx, dy);
+        
+        PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter);
+
+        double radius = polar.getRadius();
+        if(radius > viewRadius) return delegate.inverseTransform(viewPoint);
+        
+        double mag = magnification;
+        radius /= mag;
+        polar.setRadius(radius);
+        Point2D projectedPoint = PolarPoint.polarToCartesian(polar);
+        projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY());
+        Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(),
+                projectedPoint.getY()+viewCenter.getY());
+        return delegate.inverseTransform(translatedBack);
+    }
+    
+    /**
+     * Magnifies the point, without considering the Lens.
+     * @param graphPoint the point to transform via magnification
+     * @return the transformed point
+     */
+    public Point2D magnify(Point2D graphPoint) {
+        if(graphPoint == null) return null;
+        Point2D viewCenter = getViewCenter();
+        double ratio = getRatio();
+        // transform the point from the graph to the view
+        Point2D viewPoint = graphPoint;
+        // calculate point from center
+        double dx = viewPoint.getX() - viewCenter.getX();
+        double dy = viewPoint.getY() - viewCenter.getY();
+        // factor out ellipse
+        dx *= ratio;
+        Point2D pointFromCenter = new Point2D.Double(dx, dy);
+        
+        PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter);
+        double theta = polar.getTheta();
+        double radius = polar.getRadius();
+        
+        double mag = magnification;
+        radius *= mag;
+        
+//        radius = Math.min(radius, viewRadius);
+        Point2D projectedPoint = PolarPoint.polarToCartesian(theta, radius);
+        projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY());
+        Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(),
+                projectedPoint.getY()+viewCenter.getY());
+        return translatedBack;
+    }
+
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/MutableAffineTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/MutableAffineTransformer.java
new file mode 100644
index 0000000..c987e54
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/MutableAffineTransformer.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Apr 16, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+
+import javax.swing.event.ChangeListener;
+import javax.swing.event.EventListenerList;
+
+import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer;
+import edu.uci.ics.jung.visualization.util.ChangeEventSupport;
+import edu.uci.ics.jung.visualization.util.DefaultChangeEventSupport;
+
+/**
+ *
+ * Provides methods to mutate the AffineTransform used by AffineTransformer
+ * base class to map points from one coordinate system to
+ * another.
+ * 
+ * 
+ * @author Tom Nelson
+ *
+ * 
+ */
+public class MutableAffineTransformer extends AffineTransformer 
+implements MutableTransformer, ShapeTransformer, ChangeEventSupport {
+
+    protected ChangeEventSupport changeSupport =
+        new DefaultChangeEventSupport(this);
+    
+    /**
+     * create an instance that does not transform points
+     */
+    public MutableAffineTransformer() {
+        // nothing left to do
+    }
+
+    /**
+     * Create an instance with the supplied transform
+     * @param transform the transform to use
+     */
+    public MutableAffineTransformer(AffineTransform transform) {
+        super(transform);
+    }
+
+    public String toString() {
+        return "MutableAffineTransformer using "+transform;
+    }
+
+    /**
+     * setter for the scale
+     * fires a PropertyChangeEvent with the AffineTransforms representing
+     * the previous and new values for scale and offset
+     * @param scalex the amount to scale in the x direction
+     * @param scaley the amount to scale in the y direction
+     * @param from the point to transform
+     */
+    public void scale(double scalex, double scaley, Point2D from) {
+        AffineTransform xf = AffineTransform.getTranslateInstance(from.getX(),from.getY());
+        xf.scale(scalex, scaley);
+        xf.translate(-from.getX(), -from.getY());
+        inverse = null;
+        transform.preConcatenate(xf);
+        fireStateChanged();
+    }
+    
+    /**
+     * setter for the scale
+     * fires a PropertyChangeEvent with the AffineTransforms representing
+     * the previous and new values for scale and offset
+     * @param scalex the amount to scale in the x direction
+     * @param scaley the amount to scale in the y direction
+     * @param from the point to transform
+     */
+    public void setScale(double scalex, double scaley, Point2D from) {
+        transform.setToIdentity();
+        scale(scalex, scaley, from);
+    }
+    
+    /**
+     * shears the transform by passed parameters
+     * @param shx x value to shear
+     * @param shy y value to shear
+     * @param from the point to transform
+     */
+    public void shear(double shx, double shy, Point2D from) {
+        inverse = null;
+        AffineTransform at = 
+            AffineTransform.getTranslateInstance(from.getX(), from.getY());
+        at.shear(shx, shy);
+        at.translate(-from.getX(), -from.getY());
+        transform.preConcatenate(at);
+        fireStateChanged();
+    }
+    
+    /**
+     * Replace the Transform's translate x and y values
+     * with the passed values, leaving the scale values
+     * unchanged.
+     * @param tx the x value of the translation
+     * @param ty the y value of the translation
+     */
+    public void setTranslate(double tx, double ty) {
+        float scalex = (float) transform.getScaleX();
+        float scaley = (float) transform.getScaleY();
+        float shearx = (float) transform.getShearX();
+        float sheary = (float) transform.getShearY();
+        inverse = null;
+        transform.setTransform(scalex, 
+                sheary, 
+                shearx, 
+                scaley,
+                tx, ty);
+        fireStateChanged();
+    }
+    
+    /**
+     * Apply the passed values to the current Transform
+     * @param offsetx the x-value
+     * @param offsety the y-value
+     */
+    public void translate(double offsetx, double offsety) {
+        inverse = null;
+        transform.translate(offsetx, offsety);
+        fireStateChanged();
+    }
+    
+    /**
+     * preconcatenates the rotation at the supplied point with the current transform
+     * @param theta the angle by which to rotate the point
+     * @param from the point to transform
+     */
+    public void rotate(double theta, Point2D from) {
+        AffineTransform rotate = 
+            AffineTransform.getRotateInstance(theta, from.getX(), from.getY());
+        inverse = null;
+        transform.preConcatenate(rotate);
+
+        fireStateChanged();
+    }
+    
+    /**
+     * rotates the current transform at the supplied points
+     * @param radians angle by which to rotate the supplied coordinates
+     * @param x the x coordinate of the point to transform
+     * @param y the y coordinate of the point to transform
+     */
+    public void rotate(double radians, double x, double y) {
+        inverse = null;
+        transform.rotate(radians, x, y);
+        fireStateChanged();
+    }
+    
+    public void concatenate(AffineTransform xform) {
+        inverse = null;
+        transform.concatenate(xform);
+        fireStateChanged();
+        
+    }
+    public void preConcatenate(AffineTransform xform) {
+        inverse = null;
+        transform.preConcatenate(xform);
+        fireStateChanged();
+    }   
+
+    
+    /**
+     * Adds a <code>ChangeListener</code>.
+     * @param l the listener to be added
+     */
+    public void addChangeListener(ChangeListener l) {
+        changeSupport.addChangeListener(l);
+    }
+    
+    /**
+     * Removes a ChangeListener.
+     * @param l the listener to be removed
+     */
+    public void removeChangeListener(ChangeListener l) {
+        changeSupport.removeChangeListener(l);
+    }
+    
+    /**
+     * Returns an array of all the <code>ChangeListener</code>s added
+     * with addChangeListener().
+     *
+     * @return all of the <code>ChangeListener</code>s added or an empty
+     *         array if no listeners have been added
+     */
+    public ChangeListener[] getChangeListeners() {
+        return changeSupport.getChangeListeners();
+    }
+
+    /**
+     * Notifies all listeners that have registered interest for
+     * notification on this event type.  The event instance 
+     * is lazily created.
+     * @see EventListenerList
+     */
+    public void fireStateChanged() {
+        changeSupport.fireStateChanged();
+    }
+    
+    public void setToIdentity() {
+        inverse = null;
+        transform.setToIdentity();
+        fireStateChanged();
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/MutableTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/MutableTransformer.java
new file mode 100644
index 0000000..c446843
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/MutableTransformer.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 3, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer;
+import edu.uci.ics.jung.visualization.util.ChangeEventSupport;
+
+/**
+ * Provides an API for the mutation of a Function
+ * and for adding listeners for changes on the Function
+ * 
+ * @author Tom Nelson 
+ *
+ *
+ */
+public interface MutableTransformer extends ShapeTransformer, ChangeEventSupport {
+    
+    void translate(double dx, double dy);
+    
+    void setTranslate(double dx, double dy);
+    
+    void scale(double sx, double sy, Point2D point);
+    
+    void setScale(double sx, double sy, Point2D point);
+    
+    void rotate(double radians, Point2D point);
+    
+    void rotate(double radians, double x, double y);
+    
+    void shear(double shx, double shy, Point2D from);
+    
+    void concatenate(AffineTransform transform);
+    
+    void preConcatenate(AffineTransform transform);
+    
+    double getScaleX();
+    
+    double getScaleY();
+    
+    double getScale();
+    
+    double getTranslateX();
+    
+    double getTranslateY();
+    
+    double getShearX();
+    
+    double getShearY();
+
+    AffineTransform getTransform();
+    
+    void setToIdentity();
+    
+    double getRotation();
+    
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/MutableTransformerDecorator.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/MutableTransformerDecorator.java
new file mode 100644
index 0000000..36701be
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/MutableTransformerDecorator.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.visualization.transform;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+
+import javax.swing.event.ChangeListener;
+
+/**
+ * a complete decorator that wraps a MutableTransformer. Subclasses
+ * use this to allow them to only declare methods they need to change.
+ * 
+ * @author Tom Nelson 
+ *
+ */
+public abstract class MutableTransformerDecorator implements MutableTransformer {
+	
+	protected MutableTransformer delegate;
+	
+	public MutableTransformerDecorator(MutableTransformer delegate) {
+		if(delegate == null) {
+			delegate = new MutableAffineTransformer();
+		}
+		this.delegate = delegate;
+	}
+	
+	public MutableTransformer getDelegate() {
+		return delegate;
+	}
+
+	public void setDelegate(MutableTransformer delegate) {
+		this.delegate = delegate;
+	}
+
+	public void addChangeListener(ChangeListener l) {
+		delegate.addChangeListener(l);
+	}
+
+	public void concatenate(AffineTransform transform) {
+		delegate.concatenate(transform);
+	}
+
+	public void fireStateChanged() {
+		delegate.fireStateChanged();
+	}
+
+	public ChangeListener[] getChangeListeners() {
+		return delegate.getChangeListeners();
+	}
+
+	public double getScale() {
+		return delegate.getScale();
+	}
+
+	public double getScaleX() {
+		return delegate.getScaleX();
+	}
+
+	public double getScaleY() {
+		return delegate.getScaleY();
+	}
+
+	public double getShearX() {
+		return delegate.getShearX();
+	}
+
+	public double getShearY() {
+		return delegate.getShearY();
+	}
+
+	public AffineTransform getTransform() {
+		return delegate.getTransform();
+	}
+
+	public double getTranslateX() {
+		return delegate.getTranslateX();
+	}
+
+	public double getTranslateY() {
+		return delegate.getTranslateY();
+	}
+
+	public Point2D inverseTransform(Point2D p) {
+		return delegate.inverseTransform(p);
+	}
+
+	public Shape inverseTransform(Shape shape) {
+		return delegate.inverseTransform(shape);
+	}
+
+	public void preConcatenate(AffineTransform transform) {
+		delegate.preConcatenate(transform);
+	}
+
+	public void removeChangeListener(ChangeListener l) {
+		delegate.removeChangeListener(l);
+	}
+
+	public void rotate(double radians, Point2D point) {
+		delegate.rotate(radians, point);
+	}
+
+	public void scale(double sx, double sy, Point2D point) {
+		delegate.scale(sx, sy, point);
+	}
+
+	public void setScale(double sx, double sy, Point2D point) {
+		delegate.setScale(sx, sy, point);
+	}
+
+	public void setToIdentity() {
+		delegate.setToIdentity();
+	}
+
+	public void setTranslate(double dx, double dy) {
+		delegate.setTranslate(dx, dy);
+	}
+
+	public void shear(double shx, double shy, Point2D from) {
+		delegate.shear(shx, shy, from);
+	}
+
+	public Point2D transform(Point2D p) {
+		return delegate.transform(p);
+	}
+
+	public Shape transform(Shape shape) {
+		return delegate.transform(shape);
+	}
+
+	public void translate(double dx, double dy) {
+		delegate.translate(dx, dy);
+	}
+
+    public double getRotation() {
+        return delegate.getRotation();
+    }
+
+    public void rotate(double radians, double x, double y) {
+        delegate.rotate(radians, x, y);
+    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/package.html b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/package.html
new file mode 100644
index 0000000..a83ae23
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Visualization mechanisms related to transformations, including lens effects.
+
+</body>
+</html>
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/Graphics2DWrapper.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/Graphics2DWrapper.java
new file mode 100644
index 0000000..e6acbf6
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/Graphics2DWrapper.java
@@ -0,0 +1,674 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 11, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform.shape;
+
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.Image;
+import java.awt.Paint;
+import java.awt.Polygon;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.RenderingHints.Key;
+import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.ImageObserver;
+import java.awt.image.RenderedImage;
+import java.awt.image.renderable.RenderableImage;
+import java.text.AttributedCharacterIterator;
+import java.util.Map;
+
+
+/**
+ * a complete wrapping of Graphics2D, useful as a base class.
+ * Contains no additional methods, other than direct calls
+ * to the delegate.
+ * 
+ * @see GraphicsDecorator as an example subclass that
+ * adds additional methods.
+ * 
+ * @author Tom Nelson 
+ *
+ *
+ */
+public class Graphics2DWrapper {
+    
+    protected Graphics2D delegate;
+    
+    public Graphics2DWrapper() {
+        this(null);
+    }
+    public Graphics2DWrapper(Graphics2D delegate) {
+        this.delegate = delegate;
+    }
+    
+    public void setDelegate(Graphics2D delegate) {
+        this.delegate = delegate;
+    }
+    
+    public Graphics2D getDelegate() {
+        return delegate;
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#addRenderingHints(java.util.Map)
+     */
+    public void addRenderingHints(Map<?,?> hints) {
+        delegate.addRenderingHints(hints);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#clearRect(int, int, int, int)
+     */
+    public void clearRect(int x, int y, int width, int height) {
+        delegate.clearRect(x, y, width, height);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#clip(java.awt.Shape)
+     */
+    public void clip(Shape s) {
+        delegate.clip(s);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#clipRect(int, int, int, int)
+     */
+    public void clipRect(int x, int y, int width, int height) {
+        delegate.clipRect(x, y, width, height);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#copyArea(int, int, int, int, int, int)
+     */
+    public void copyArea(int x, int y, int width, int height, int dx, int dy) {
+        delegate.copyArea(x, y, width, height, dx, dy);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#create()
+     */
+    public Graphics create() {
+        return delegate.create();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#create(int, int, int, int)
+     */
+    public Graphics create(int x, int y, int width, int height) {
+        return delegate.create(x, y, width, height);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#dispose()
+     */
+    public void dispose() {
+        delegate.dispose();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#draw(java.awt.Shape)
+     */
+    public void draw(Shape s) {
+        delegate.draw(s);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#draw3DRect(int, int, int, int, boolean)
+     */
+    public void draw3DRect(int x, int y, int width, int height, boolean raised) {
+        delegate.draw3DRect(x, y, width, height, raised);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawArc(int, int, int, int, int, int)
+     */
+    public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
+        delegate.drawArc(x, y, width, height, startAngle, arcAngle);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawBytes(byte[], int, int, int, int)
+     */
+    public void drawBytes(byte[] data, int offset, int length, int x, int y) {
+        delegate.drawBytes(data, offset, length, x, y);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawChars(char[], int, int, int, int)
+     */
+    public void drawChars(char[] data, int offset, int length, int x, int y) {
+        delegate.drawChars(data, offset, length, x, y);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#drawGlyphVector(java.awt.font.GlyphVector, float, float)
+     */
+    public void drawGlyphVector(GlyphVector g, float x, float y) {
+        delegate.drawGlyphVector(g, x, y);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#drawImage(java.awt.image.BufferedImage, java.awt.image.BufferedImageOp, int, int)
+     */
+    public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
+        delegate.drawImage(img, op, x, y);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#drawImage(java.awt.Image, java.awt.geom.AffineTransform, java.awt.image.ImageObserver)
+     */
+    public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {
+        return delegate.drawImage(img, xform, obs);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, java.awt.Color, java.awt.image.ImageObserver)
+     */
+    public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) {
+        return delegate.drawImage(img, x, y, bgcolor, observer);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, java.awt.image.ImageObserver)
+     */
+    public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
+        return delegate.drawImage(img, x, y, observer);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, int, int, java.awt.Color, java.awt.image.ImageObserver)
+     */
+    public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) {
+        return delegate.drawImage(img, x, y, width, height, bgcolor, observer);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, int, int, java.awt.image.ImageObserver)
+     */
+    public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) {
+        return delegate.drawImage(img, x, y, width, height, observer);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, int, int, int, int, int, int, java.awt.Color, java.awt.image.ImageObserver)
+     */
+    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) {
+        return delegate.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawImage(java.awt.Image, int, int, int, int, int, int, int, int, java.awt.image.ImageObserver)
+     */
+    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
+        return delegate.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer);
+    }
+    
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawLine(int, int, int, int)
+     */
+    public void drawLine(int x1, int y1, int x2, int y2) {
+        delegate.drawLine(x1, y1, x2, y2);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawOval(int, int, int, int)
+     */
+    public void drawOval(int x, int y, int width, int height) {
+        delegate.drawOval(x, y, width, height);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawPolygon(int[], int[], int)
+     */
+    public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
+        delegate.drawPolygon(xPoints, yPoints, nPoints);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawPolygon(java.awt.Polygon)
+     */
+    public void drawPolygon(Polygon p) {
+        delegate.drawPolygon(p);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawPolyline(int[], int[], int)
+     */
+    public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
+        delegate.drawPolyline(xPoints, yPoints, nPoints);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawRect(int, int, int, int)
+     */
+    public void drawRect(int x, int y, int width, int height) {
+        delegate.drawRect(x, y, width, height);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#drawRenderableImage(java.awt.image.renderable.RenderableImage, java.awt.geom.AffineTransform)
+     */
+    public void drawRenderableImage(RenderableImage img, AffineTransform xform) {
+        delegate.drawRenderableImage(img, xform);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#drawRenderedImage(java.awt.image.RenderedImage, java.awt.geom.AffineTransform)
+     */
+    public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
+        delegate.drawRenderedImage(img, xform);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#drawRoundRect(int, int, int, int, int, int)
+     */
+    public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
+        delegate.drawRoundRect(x, y, width, height, arcWidth, arcHeight);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#drawString(java.text.AttributedCharacterIterator, float, float)
+     */
+    public void drawString(AttributedCharacterIterator iterator, float x, float y) {
+        delegate.drawString(iterator, x, y);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#drawString(java.text.AttributedCharacterIterator, int, int)
+     */
+    public void drawString(AttributedCharacterIterator iterator, int x, int y) {
+        delegate.drawString(iterator, x, y);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#drawString(java.lang.String, float, float)
+     */
+    public void drawString(String s, float x, float y) {
+        delegate.drawString(s, x, y);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#drawString(java.lang.String, int, int)
+     */
+    public void drawString(String str, int x, int y) {
+        delegate.drawString(str, x, y);
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    public boolean equals(Object obj) {
+        return delegate.equals(obj);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#fill(java.awt.Shape)
+     */
+    public void fill(Shape s) {
+        delegate.fill(s);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#fill3DRect(int, int, int, int, boolean)
+     */
+    public void fill3DRect(int x, int y, int width, int height, boolean raised) {
+        delegate.fill3DRect(x, y, width, height, raised);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#fillArc(int, int, int, int, int, int)
+     */
+    public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
+        delegate.fillArc(x, y, width, height, startAngle, arcAngle);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#fillOval(int, int, int, int)
+     */
+    public void fillOval(int x, int y, int width, int height) {
+        delegate.fillOval(x, y, width, height);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#fillPolygon(int[], int[], int)
+     */
+    public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
+        delegate.fillPolygon(xPoints, yPoints, nPoints);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#fillPolygon(java.awt.Polygon)
+     */
+    public void fillPolygon(Polygon p) {
+        delegate.fillPolygon(p);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#fillRect(int, int, int, int)
+     */
+    public void fillRect(int x, int y, int width, int height) {
+        delegate.fillRect(x, y, width, height);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#fillRoundRect(int, int, int, int, int, int)
+     */
+    public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
+        delegate.fillRoundRect(x, y, width, height, arcWidth, arcHeight);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#finalize()
+     */
+    public void finalize() {
+        delegate.finalize();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#getBackground()
+     */
+    public Color getBackground() {
+        return delegate.getBackground();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#getClip()
+     */
+    public Shape getClip() {
+        return delegate.getClip();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#getClipBounds()
+     */
+    public Rectangle getClipBounds() {
+        return delegate.getClipBounds();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#getClipBounds(java.awt.Rectangle)
+     */
+    public Rectangle getClipBounds(Rectangle r) {
+        return delegate.getClipBounds(r);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#getClipRect()
+     */
+    @SuppressWarnings("deprecation")
+    public Rectangle getClipRect() {
+        return delegate.getClipRect();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#getColor()
+     */
+    public Color getColor() {
+        return delegate.getColor();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#getComposite()
+     */
+    public Composite getComposite() {
+        return delegate.getComposite();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#getDeviceConfiguration()
+     */
+    public GraphicsConfiguration getDeviceConfiguration() {
+        return delegate.getDeviceConfiguration();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#getFont()
+     */
+    public Font getFont() {
+        return delegate.getFont();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#getFontMetrics()
+     */
+    public FontMetrics getFontMetrics() {
+        return delegate.getFontMetrics();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#getFontMetrics(java.awt.Font)
+     */
+    public FontMetrics getFontMetrics(Font f) {
+        return delegate.getFontMetrics(f);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#getFontRenderContext()
+     */
+    public FontRenderContext getFontRenderContext() {
+        return delegate.getFontRenderContext();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#getPaint()
+     */
+    public Paint getPaint() {
+        return delegate.getPaint();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#getRenderingHint(java.awt.RenderingHints.Key)
+     */
+    public Object getRenderingHint(Key hintKey) {
+        return delegate.getRenderingHint(hintKey);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#getRenderingHints()
+     */
+    public RenderingHints getRenderingHints() {
+        return delegate.getRenderingHints();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#getStroke()
+     */
+    public Stroke getStroke() {
+        return delegate.getStroke();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#getTransform()
+     */
+    public AffineTransform getTransform() {
+        return delegate.getTransform();
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    public int hashCode() {
+        return delegate.hashCode();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#hit(java.awt.Rectangle, java.awt.Shape, boolean)
+     */
+    public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
+        return delegate.hit(rect, s, onStroke);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#hitClip(int, int, int, int)
+     */
+    public boolean hitClip(int x, int y, int width, int height) {
+        return delegate.hitClip(x, y, width, height);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#rotate(double, double, double)
+     */
+    public void rotate(double theta, double x, double y) {
+        delegate.rotate(theta, x, y);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#rotate(double)
+     */
+    public void rotate(double theta) {
+        delegate.rotate(theta);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#scale(double, double)
+     */
+    public void scale(double sx, double sy) {
+        delegate.scale(sx, sy);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#setBackground(java.awt.Color)
+     */
+    public void setBackground(Color color) {
+        delegate.setBackground(color);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#setClip(int, int, int, int)
+     */
+    public void setClip(int x, int y, int width, int height) {
+        delegate.setClip(x, y, width, height);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#setClip(java.awt.Shape)
+     */
+    public void setClip(Shape clip) {
+        delegate.setClip(clip);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#setColor(java.awt.Color)
+     */
+    public void setColor(Color c) {
+        delegate.setColor(c);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#setComposite(java.awt.Composite)
+     */
+    public void setComposite(Composite comp) {
+        delegate.setComposite(comp);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#setFont(java.awt.Font)
+     */
+    public void setFont(Font font) {
+        delegate.setFont(font);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#setPaint(java.awt.Paint)
+     */
+    public void setPaint(Paint paint) {
+        delegate.setPaint(paint);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#setPaintMode()
+     */
+    public void setPaintMode() {
+        delegate.setPaintMode();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#setRenderingHint(java.awt.RenderingHints.Key, java.lang.Object)
+     */
+    public void setRenderingHint(Key hintKey, Object hintValue) {
+        delegate.setRenderingHint(hintKey, hintValue);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#setRenderingHints(java.util.Map)
+     */
+    public void setRenderingHints(Map<?,?> hints) {
+        delegate.setRenderingHints(hints);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#setStroke(java.awt.Stroke)
+     */
+    public void setStroke(Stroke s) {
+        delegate.setStroke(s);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#setTransform(java.awt.geom.AffineTransform)
+     */
+    public void setTransform(AffineTransform Tx) {
+        delegate.setTransform(Tx);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#setXORMode(java.awt.Color)
+     */
+    public void setXORMode(Color c1) {
+        delegate.setXORMode(c1);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#shear(double, double)
+     */
+    public void shear(double shx, double shy) {
+        delegate.shear(shx, shy);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics#toString()
+     */
+    public String toString() {
+        return delegate.toString();
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#transform(java.awt.geom.AffineTransform)
+     */
+    public void transform(AffineTransform Tx) {
+        delegate.transform(Tx);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#translate(double, double)
+     */
+    public void translate(double tx, double ty) {
+        delegate.translate(tx, ty);
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.Graphics2D#translate(int, int)
+     */
+    public void translate(int x, int y) {
+        delegate.translate(x, y);
+    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/GraphicsDecorator.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/GraphicsDecorator.java
new file mode 100644
index 0000000..2fd8cf7
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/GraphicsDecorator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 11, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform.shape;
+
+import java.awt.Component;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+
+import javax.swing.CellRendererPane;
+import javax.swing.Icon;
+
+
+/**
+ * an extendion of Graphics2DWrapper that adds enhanced
+ * methods for drawing icons and components
+ * 
+ * @see TransformingGraphics as an example subclass
+ * 
+ * @author Tom Nelson 
+ *
+ *
+ */
+public class GraphicsDecorator extends Graphics2DWrapper {
+    
+    public GraphicsDecorator() {
+        this(null);
+    }
+    public GraphicsDecorator(Graphics2D delegate) {
+        super(delegate);
+    }
+    
+    public void draw(Icon icon, Component c, Shape clip, int x, int y) {
+    	int w = icon.getIconWidth();
+    	int h = icon.getIconHeight();
+    	icon.paintIcon(c, delegate, x-w/2, y-h/2);
+    }
+    
+    public void draw(Component c, CellRendererPane rendererPane, 
+    		int x, int y, int w, int h, boolean shouldValidate) {
+    	rendererPane.paintComponent(delegate, c, c.getParent(), x, y, w, h, shouldValidate);
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/HyperbolicShapeTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/HyperbolicShapeTransformer.java
new file mode 100644
index 0000000..3510e44
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/HyperbolicShapeTransformer.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.visualization.transform.shape;
+
+import java.awt.Component;
+import java.awt.Shape;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.algorithms.layout.PolarPoint;
+import edu.uci.ics.jung.visualization.transform.HyperbolicTransformer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+
+/**
+ * HyperbolicShapeTransformer extends HyperbolicTransformer and
+ * adds implementations for methods in ShapeFlatnessTransformer.
+ * It modifies the shapes (Vertex, Edge, and Arrowheads) so that
+ * they are distorted by the hyperbolic transformation
+ * 
+ * @author Tom Nelson
+ *
+ *
+ */
+public class HyperbolicShapeTransformer extends HyperbolicTransformer 
+    implements ShapeFlatnessTransformer {
+
+    /**
+     * Create an instance, setting values from the passed component
+     * and registering to listen for size changes on the component.
+     * @param component the component in which rendering takes place
+     */
+    public HyperbolicShapeTransformer(Component component) {
+        this(component, null);
+    }
+    
+    /**
+     * Create an instance, setting values from the passed component
+     * and registering to listen for size changes on the component,
+     * with a possibly shared transform <code>delegate</code>.
+     * @param component the component in which rendering takes place
+     * @param delegate the transformer to use
+     */
+    public HyperbolicShapeTransformer(Component component, MutableTransformer delegate) {
+        super(component, delegate);
+   }
+    
+    /**
+     * Transform the supplied shape with the overridden transform
+     * method so that the shape is distorted by the hyperbolic 
+     * transform.
+     * @param shape a shape to transform
+     * @return a GeneralPath for the transformed shape
+     */
+    public Shape transform(Shape shape) {
+        return transform(shape, 0);
+    }
+    public Shape transform(Shape shape, float flatness) {
+        GeneralPath newPath = new GeneralPath();
+        float[] coords = new float[6];
+        PathIterator iterator = null;
+        if(flatness == 0) {
+            iterator = shape.getPathIterator(null);
+        } else {
+            iterator = shape.getPathIterator(null, flatness);
+        }
+        for( ;
+            iterator.isDone() == false;
+            iterator.next()) {
+            int type = iterator.currentSegment(coords);
+            switch(type) {
+            case PathIterator.SEG_MOVETO:
+                Point2D p = _transform(new Point2D.Float(coords[0], coords[1]));
+                newPath.moveTo((float)p.getX(), (float)p.getY());
+                break;
+                
+            case PathIterator.SEG_LINETO:
+                p = _transform(new Point2D.Float(coords[0], coords[1]));
+                newPath.lineTo((float)p.getX(), (float) p.getY());
+                break;
+                
+            case PathIterator.SEG_QUADTO:
+                p = _transform(new Point2D.Float(coords[0], coords[1]));
+                Point2D q = _transform(new Point2D.Float(coords[2], coords[3]));
+                newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY());
+                break;
+                
+            case PathIterator.SEG_CUBICTO:
+                p = _transform(new Point2D.Float(coords[0], coords[1]));
+                q = _transform(new Point2D.Float(coords[2], coords[3]));
+                Point2D r = _transform(new Point2D.Float(coords[4], coords[5]));
+                newPath.curveTo((float)p.getX(), (float)p.getY(), 
+                        (float)q.getX(), (float)q.getY(),
+                        (float)r.getX(), (float)r.getY());
+                break;
+                
+            case PathIterator.SEG_CLOSE:
+                newPath.closePath();
+                break;
+                    
+            }
+        }
+        return newPath;
+    }
+
+    public Shape inverseTransform(Shape shape) {
+        GeneralPath newPath = new GeneralPath();
+        float[] coords = new float[6];
+        for(PathIterator iterator=shape.getPathIterator(null);
+            iterator.isDone() == false;
+            iterator.next()) {
+            int type = iterator.currentSegment(coords);
+            switch(type) {
+            case PathIterator.SEG_MOVETO:
+                Point2D p = _inverseTransform(new Point2D.Float(coords[0], coords[1]));
+                newPath.moveTo((float)p.getX(), (float)p.getY());
+                break;
+                
+            case PathIterator.SEG_LINETO:
+                p = _inverseTransform(new Point2D.Float(coords[0], coords[1]));
+                newPath.lineTo((float)p.getX(), (float) p.getY());
+                break;
+                
+            case PathIterator.SEG_QUADTO:
+                p = _inverseTransform(new Point2D.Float(coords[0], coords[1]));
+                Point2D q = _inverseTransform(new Point2D.Float(coords[2], coords[3]));
+                newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY());
+                break;
+                
+            case PathIterator.SEG_CUBICTO:
+                p = _inverseTransform(new Point2D.Float(coords[0], coords[1]));
+                q = _inverseTransform(new Point2D.Float(coords[2], coords[3]));
+                Point2D r = _inverseTransform(new Point2D.Float(coords[4], coords[5]));
+                newPath.curveTo((float)p.getX(), (float)p.getY(), 
+                        (float)q.getX(), (float)q.getY(),
+                        (float)r.getX(), (float)r.getY());
+                break;
+                
+            case PathIterator.SEG_CLOSE:
+                newPath.closePath();
+                break;
+                    
+            }
+        }
+        return newPath;
+    }
+    /**
+     * override base class transform to project the fisheye effect
+     */
+    private Point2D _transform(Point2D graphPoint) {
+        if(graphPoint == null) return null;
+        Point2D viewCenter = getViewCenter();
+        double viewRadius = getViewRadius();
+        double ratio = getRatio();
+        // transform the point from the graph to the view
+        Point2D viewPoint = graphPoint;//delegate.transform(graphPoint);
+        // calculate point from center
+        double dx = viewPoint.getX() - viewCenter.getX();
+        double dy = viewPoint.getY() - viewCenter.getY();
+        // factor out ellipse
+        dx *= ratio;
+        Point2D pointFromCenter = new Point2D.Double(dx, dy);
+        
+        PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter);
+        double theta = polar.getTheta();
+        double radius = polar.getRadius();
+        if(radius > viewRadius) return viewPoint;
+        
+        double mag = Math.tan(Math.PI/2*magnification);
+        radius *= mag;
+        
+        radius = Math.min(radius, viewRadius);
+        radius /= viewRadius;
+        radius *= Math.PI/2;
+        radius = Math.abs(Math.atan(radius));
+        radius *= viewRadius;
+        Point2D projectedPoint = PolarPoint.polarToCartesian(theta, radius);
+        projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY());
+        Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(),
+                projectedPoint.getY()+viewCenter.getY());
+        return translatedBack;
+    }
+    
+    /**
+     * override base class to un-project the fisheye effect
+     */
+    private Point2D _inverseTransform(Point2D viewPoint) {
+    	
+        viewPoint = delegate.inverseTransform(viewPoint);
+        Point2D viewCenter = getViewCenter();
+        double viewRadius = getViewRadius();
+        double ratio = getRatio();
+        double dx = viewPoint.getX() - viewCenter.getX();
+        double dy = viewPoint.getY() - viewCenter.getY();
+        // factor out ellipse
+        dx *= ratio;
+
+        Point2D pointFromCenter = new Point2D.Double(dx, dy);
+        
+        PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter);
+
+        double radius = polar.getRadius();
+        if(radius > viewRadius) return viewPoint;//elegate.inverseTransform(viewPoint);
+        
+        radius /= viewRadius;
+        radius = Math.abs(Math.tan(radius));
+        radius /= Math.PI/2;
+        radius *= viewRadius;
+        double mag = Math.tan(Math.PI/2*magnification);
+        radius /= mag;
+        polar.setRadius(radius);
+        Point2D projectedPoint = PolarPoint.polarToCartesian(polar);
+        projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY());
+        Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(),
+                projectedPoint.getY()+viewCenter.getY());
+        return translatedBack;
+        //delegate.inverseTransform(translatedBack);
+    }
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/Intersector.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/Intersector.java
new file mode 100644
index 0000000..403eadb
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/Intersector.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.visualization.transform.shape;
+
+import java.awt.Rectangle;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.util.HashSet;
+import java.util.Set;
+
+public class Intersector {
+    
+    protected Rectangle rectangle;
+    Line2D line;
+    Set<Point2D> points = new HashSet<Point2D>();
+
+    public Intersector(Rectangle rectangle) {
+        this.rectangle = rectangle;
+    }
+
+    public Intersector(Rectangle rectangle, Line2D line) {
+        this.rectangle = rectangle;
+       intersectLine(line);
+    }
+    
+    public void intersectLine(Line2D line) {
+        this.line = line;
+        points.clear();
+        float rx0 = (float) rectangle.getMinX();
+        float ry0 = (float) rectangle.getMinY();
+        float rx1 = (float) rectangle.getMaxX();
+        float ry1 = (float) rectangle.getMaxY();
+        
+        float x1 = (float) line.getX1();
+        float y1 = (float) line.getY1();
+        float x2 = (float) line.getX2();
+        float y2 = (float) line.getY2();
+        
+        float dy = y2 - y1;
+        float dx = x2 - x1;
+        
+        if(dx != 0) {
+            float m = dy/dx;
+            float b = y1 - m*x1;
+            
+            // base of rect where y == ry0
+            float x = (ry0 - b) / m;
+            
+            if(rx0 <= x && x <= rx1) {
+                points.add(new Point2D.Float(x, ry0));
+            }
+            
+            // top where y == ry1
+            x = (ry1 - b) / m;
+            if(rx0 <= x && x <= rx1) {
+                points.add(new Point2D.Float(x, ry1));
+            }
+            
+            // left side, where x == rx0
+            float y = m * rx0 + b;
+            if(ry0 <= y && y <= ry1) {
+                points.add(new Point2D.Float(rx0, y));
+            }
+            
+            
+            // right side, where x == rx1
+            y = m * rx1 + b;
+            if(ry0 <= y && y <= ry1) {
+                points.add(new Point2D.Float(rx1, y));
+            }
+            
+        } else {
+            
+            // base, where y == ry0
+            float x = x1;
+            if(rx0 <= x && x <= rx1) {
+                points.add(new Point2D.Float(x, ry0));
+            }
+            
+            // top, where y == ry1
+            x = x2;
+            if(rx0 <= x && x <= rx1) {
+                points.add(new Point2D.Float(x, ry1));
+            }
+        }
+    }
+    public Line2D getLine() {
+        return line;
+    }
+    public Set<Point2D> getPoints() {
+        return points;
+    }
+    public Rectangle getRectangle() {
+        return rectangle;
+    }
+
+    public String toString() {
+        return "Rectangle: "+rectangle+", points:"+points;
+    }
+    
+    public static void main(String[] args) {
+        Rectangle rectangle = new Rectangle(0,0,10,10);
+        Line2D line = new Line2D.Float(4,4,5,5);
+        System.err.println(""+new Intersector(rectangle, line));
+        System.err.println(""+new Intersector(rectangle, new Line2D.Float(9,11,11,9)));
+        System.err.println(""+new Intersector(rectangle, new Line2D.Float(1,1,3,2)));
+        System.err.println(""+new Intersector(rectangle, new Line2D.Float(4,6,6,4)));
+    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/MagnifyIconGraphics.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/MagnifyIconGraphics.java
new file mode 100644
index 0000000..71ffaca
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/MagnifyIconGraphics.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 11, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform.shape;
+
+import java.awt.Component;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
+import java.awt.geom.Rectangle2D;
+
+import javax.swing.Icon;
+
+import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer;
+
+
+/**
+ * Subclassed to apply a magnification transform to an icon.
+ *  
+ * @author Tom Nelson 
+ *
+ *
+ */
+public class MagnifyIconGraphics extends TransformingFlatnessGraphics {
+    
+    public MagnifyIconGraphics(BidirectionalTransformer transformer) {
+        this(transformer, null);
+    }
+    
+    public MagnifyIconGraphics(BidirectionalTransformer Function, Graphics2D delegate) {
+        super(Function, delegate);
+    }
+    
+    public void draw(Icon icon, Component c, Shape clip, int x, int y) {
+    	
+    	if(transformer instanceof MagnifyShapeTransformer) {
+    		MagnifyShapeTransformer mst = (MagnifyShapeTransformer)transformer;
+    		int w = icon.getIconWidth();
+    		int h = icon.getIconHeight();
+    		Rectangle2D r = new Rectangle2D.Double(x-w/2,y-h/2,w,h);
+    		Shape lens = mst.getLensShape();
+    		if(lens.intersects(r)) {
+    			// magnify the whole icon
+    			Rectangle2D s = mst.magnify(r).getBounds2D();
+    			if(lens.intersects(s)) {
+    				clip = mst.transform(clip);
+    				double sx = s.getWidth()/r.getWidth();
+    				double sy = s.getHeight()/r.getHeight();
+
+    				AffineTransform old = delegate.getTransform();
+    				AffineTransform xform = new AffineTransform(old);
+    				xform.translate(s.getMinX(), s.getMinY());
+    				xform.scale(sx, sy);
+    				xform.translate(-s.getMinX(), -s.getMinY());
+    				Shape oldClip = delegate.getClip();
+    				delegate.clip(clip);
+    				delegate.setTransform(xform);
+    				icon.paintIcon(c, delegate, (int)s.getMinX(), (int)s.getMinY());
+    				delegate.setTransform(old);
+    				delegate.setClip(oldClip);
+    			} else {
+    				// clip out the lens so the small icon doesn't get drawn
+    				// inside of it
+    				Shape oldClip = delegate.getClip();
+    				Area viewBounds = new Area(oldClip);
+    				viewBounds.subtract(new Area(lens));
+    				delegate.setClip(viewBounds);
+    				icon.paintIcon(c, delegate, (int)r.getMinX(),(int)r.getMinY());
+    				delegate.setClip(oldClip);
+    			}
+
+    		} else {
+    			icon.paintIcon(c, delegate, (int)r.getMinX(),(int)r.getMinY());
+    		}
+    	}
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/MagnifyImageLensSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/MagnifyImageLensSupport.java
new file mode 100644
index 0000000..072a1c4
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/MagnifyImageLensSupport.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 21, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform.shape;
+
+import java.awt.Dimension;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.control.ModalLensGraphMouse;
+import edu.uci.ics.jung.visualization.picking.ViewLensShapePickSupport;
+import edu.uci.ics.jung.visualization.renderers.BasicRenderer;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+import edu.uci.ics.jung.visualization.renderers.ReshapingEdgeRenderer;
+import edu.uci.ics.jung.visualization.transform.AbstractLensSupport;
+import edu.uci.ics.jung.visualization.transform.LensTransformer;
+/**
+ * Changes various visualization settings to activate or deactivate an
+ * examining lens for a jung graph application. 
+ * 
+ * @author Tom Nelson
+ */
+public class MagnifyImageLensSupport<V,E> extends AbstractLensSupport<V,E> {
+    
+    protected RenderContext<V,E> renderContext;
+    protected GraphicsDecorator lensGraphicsDecorator;
+    protected GraphicsDecorator savedGraphicsDecorator;
+    protected Renderer<V,E> renderer;
+    protected Renderer<V,E> transformingRenderer;
+    protected GraphElementAccessor<V,E> pickSupport;
+    protected Renderer.Edge<V,E> savedEdgeRenderer;
+    protected Renderer.Edge<V,E> reshapingEdgeRenderer;
+
+    static final String instructions = 
+        "<html><center>Mouse-Drag the Lens center to move it<p>"+
+        "Mouse-Drag the Lens edge to resize it<p>"+
+        "Ctrl+MouseWheel to change magnification</center></html>";
+    
+    public MagnifyImageLensSupport(VisualizationViewer<V,E> vv) {
+        this(vv, new MagnifyShapeTransformer(vv),
+                new ModalLensGraphMouse());
+    }
+    
+    public MagnifyImageLensSupport(VisualizationViewer<V,E> vv, LensTransformer lensTransformer,
+            ModalGraphMouse lensGraphMouse) {
+        super(vv, lensGraphMouse);
+        this.renderContext = vv.getRenderContext();
+        this.pickSupport = renderContext.getPickSupport();
+        this.renderer = vv.getRenderer();
+        this.transformingRenderer = new BasicRenderer<V,E>();
+        this.savedGraphicsDecorator = renderContext.getGraphicsContext();
+        this.lensTransformer = lensTransformer;
+        this.savedEdgeRenderer = vv.getRenderer().getEdgeRenderer();
+        this.reshapingEdgeRenderer = new ReshapingEdgeRenderer<V,E>();
+        this.reshapingEdgeRenderer.setEdgeArrowRenderingSupport(savedEdgeRenderer.getEdgeArrowRenderingSupport());
+
+        Dimension d = vv.getSize();
+        if(d.width == 0 || d.height == 0) {
+            d = vv.getPreferredSize();
+        }
+        lensTransformer.setViewRadius(d.width/5);
+        this.lensGraphicsDecorator = new MagnifyIconGraphics(lensTransformer);
+    }
+    
+    public void activate() {
+    	lensTransformer.setDelegate(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW));
+        if(lens == null) {
+            lens = new Lens(lensTransformer);
+        }
+        if(lensControls == null) {
+            lensControls = new LensControls(lensTransformer);
+        }
+        renderContext.setPickSupport(new ViewLensShapePickSupport<V,E>(vv));
+        lensTransformer.setDelegate(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW));
+        vv.getRenderContext().getMultiLayerTransformer().setTransformer(Layer.VIEW, lensTransformer);
+        this.renderContext.setGraphicsContext(lensGraphicsDecorator);
+        vv.getRenderer().setEdgeRenderer(reshapingEdgeRenderer);
+        vv.addPreRenderPaintable(lens);
+        vv.addPostRenderPaintable(lensControls);
+        vv.setGraphMouse(lensGraphMouse);
+        vv.setToolTipText(instructions);
+        vv.repaint();
+    }
+    
+    public void deactivate() {
+    	renderContext.setPickSupport(pickSupport);
+    	vv.getRenderContext().getMultiLayerTransformer().setTransformer(Layer.VIEW, lensTransformer.getDelegate());
+        vv.removePreRenderPaintable(lens);
+        vv.removePostRenderPaintable(lensControls);
+        this.renderContext.setGraphicsContext(savedGraphicsDecorator);
+        vv.getRenderer().setEdgeRenderer(savedEdgeRenderer);
+        vv.setToolTipText(defaultToolTipText);
+        vv.setGraphMouse(graphMouse);
+        vv.repaint();
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/MagnifyShapeTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/MagnifyShapeTransformer.java
new file mode 100644
index 0000000..46f7afc
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/MagnifyShapeTransformer.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.visualization.transform.shape;
+
+import java.awt.Component;
+import java.awt.Shape;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+
+import edu.uci.ics.jung.algorithms.layout.PolarPoint;
+import edu.uci.ics.jung.visualization.transform.MagnifyTransformer;
+import edu.uci.ics.jung.visualization.transform.MutableTransformer;
+
+/**
+ * MagnifyShapeTransformer extends MagnifyTransformer and
+ * adds implementations for methods in ShapeTransformer.
+ * It modifies the shapes (Vertex, Edge, and Arrowheads) so that
+ * they are enlarged by the magnify transformation.
+ * 
+ * @author Tom Nelson
+ */
+public class MagnifyShapeTransformer extends MagnifyTransformer 
+    implements ShapeFlatnessTransformer {
+
+    /**
+     * Create an instance, setting values from the passed component
+     * and registering to listen for size changes on the component.
+     * 
+     * @param component the component used for rendering
+     */
+    public MagnifyShapeTransformer(Component component) {
+        this(component, null);
+    }
+    
+    /**
+     * Create an instance, setting values from the passed component
+     * and registering to listen for size changes on the component,
+     * with a possibly shared transform <code>delegate</code>.
+     * 
+     * @param component the component used for rendering
+     * @param delegate the transformer to use
+     */
+    public MagnifyShapeTransformer(Component component, MutableTransformer delegate) {
+        super(component, delegate);
+   }
+    
+    /**
+     * Transform the supplied shape with the overridden transform
+     * method so that the shape is distorted by the magnify 
+     * transform.
+     * @param shape a shape to transform
+     * @return a GeneralPath for the transformed shape
+     */
+    public Shape transform(Shape shape) {
+        return transform(shape, 0);
+    }
+    public Shape transform(Shape shape, float flatness) {
+        GeneralPath newPath = new GeneralPath();
+        float[] coords = new float[6];
+        PathIterator iterator = null;
+        if(flatness == 0) {
+            iterator = shape.getPathIterator(null);
+        } else {
+            iterator = shape.getPathIterator(null, flatness);
+        }
+        for( ;
+            iterator.isDone() == false;
+            iterator.next()) {
+            int type = iterator.currentSegment(coords);
+            switch(type) {
+            case PathIterator.SEG_MOVETO:
+                Point2D p = _transform(new Point2D.Float(coords[0], coords[1]));
+                newPath.moveTo((float)p.getX(), (float)p.getY());
+                break;
+                
+            case PathIterator.SEG_LINETO:
+                p = _transform(new Point2D.Float(coords[0], coords[1]));
+                newPath.lineTo((float)p.getX(), (float) p.getY());
+                break;
+                
+            case PathIterator.SEG_QUADTO:
+                p = _transform(new Point2D.Float(coords[0], coords[1]));
+                Point2D q = _transform(new Point2D.Float(coords[2], coords[3]));
+                newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY());
+                break;
+                
+            case PathIterator.SEG_CUBICTO:
+                p = _transform(new Point2D.Float(coords[0], coords[1]));
+                q = _transform(new Point2D.Float(coords[2], coords[3]));
+                Point2D r = _transform(new Point2D.Float(coords[4], coords[5]));
+                newPath.curveTo((float)p.getX(), (float)p.getY(), 
+                        (float)q.getX(), (float)q.getY(),
+                        (float)r.getX(), (float)r.getY());
+                break;
+                
+            case PathIterator.SEG_CLOSE:
+                newPath.closePath();
+                break;
+                    
+            }
+        }
+        return newPath;
+    }
+
+    public Shape inverseTransform(Shape shape) {
+        GeneralPath newPath = new GeneralPath();
+        float[] coords = new float[6];
+        for(PathIterator iterator=shape.getPathIterator(null);
+            iterator.isDone() == false;
+            iterator.next()) {
+            int type = iterator.currentSegment(coords);
+            switch(type) {
+            case PathIterator.SEG_MOVETO:
+                Point2D p = _inverseTransform(new Point2D.Float(coords[0], coords[1]));
+                newPath.moveTo((float)p.getX(), (float)p.getY());
+                break;
+                
+            case PathIterator.SEG_LINETO:
+                p = _inverseTransform(new Point2D.Float(coords[0], coords[1]));
+                newPath.lineTo((float)p.getX(), (float) p.getY());
+                break;
+                
+            case PathIterator.SEG_QUADTO:
+                p = _inverseTransform(new Point2D.Float(coords[0], coords[1]));
+                Point2D q = _inverseTransform(new Point2D.Float(coords[2], coords[3]));
+                newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY());
+                break;
+                
+            case PathIterator.SEG_CUBICTO:
+                p = _inverseTransform(new Point2D.Float(coords[0], coords[1]));
+                q = _inverseTransform(new Point2D.Float(coords[2], coords[3]));
+                Point2D r = _inverseTransform(new Point2D.Float(coords[4], coords[5]));
+                newPath.curveTo((float)p.getX(), (float)p.getY(), 
+                        (float)q.getX(), (float)q.getY(),
+                        (float)r.getX(), (float)r.getY());
+                break;
+                
+            case PathIterator.SEG_CLOSE:
+                newPath.closePath();
+                break;
+                    
+            }
+        }
+        return newPath;
+    }
+    
+    private Point2D _transform(Point2D graphPoint) {
+        if(graphPoint == null) return null;
+        Point2D viewCenter = getViewCenter();
+        double viewRadius = getViewRadius();
+        double ratio = getRatio();
+        // transform the point from the graph to the view
+        Point2D viewPoint = graphPoint;
+        // calculate point from center
+        double dx = viewPoint.getX() - viewCenter.getX();
+        double dy = viewPoint.getY() - viewCenter.getY();
+        // factor out ellipse
+        dx *= ratio;
+        Point2D pointFromCenter = new Point2D.Double(dx, dy);
+        
+        PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter);
+        double theta = polar.getTheta();
+        double radius = polar.getRadius();
+        if(radius > viewRadius) return viewPoint;
+        
+        double mag = magnification;
+        radius *= mag;
+        
+        radius = Math.min(radius, viewRadius);
+        Point2D projectedPoint = PolarPoint.polarToCartesian(theta, radius);
+        projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY());
+        Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(),
+                projectedPoint.getY()+viewCenter.getY());
+        return translatedBack;
+    }
+    
+    /**
+     * override base class to un-project the fisheye effect
+     */
+    private Point2D _inverseTransform(Point2D viewPoint) {
+        
+    	viewPoint = delegate.inverseTransform(viewPoint);
+        Point2D viewCenter = getViewCenter();
+        double viewRadius = getViewRadius();
+        double ratio = getRatio();
+        double dx = viewPoint.getX() - viewCenter.getX();
+        double dy = viewPoint.getY() - viewCenter.getY();
+        // factor out ellipse
+        dx *= ratio;
+
+        Point2D pointFromCenter = new Point2D.Double(dx, dy);
+        
+        PolarPoint polar = PolarPoint.cartesianToPolar(pointFromCenter);
+
+        double radius = polar.getRadius();
+        if(radius > viewRadius) return viewPoint;
+        
+        double mag = magnification;
+        radius /= mag;
+        polar.setRadius(radius);
+        Point2D projectedPoint = PolarPoint.polarToCartesian(polar);
+        projectedPoint.setLocation(projectedPoint.getX()/ratio, projectedPoint.getY());
+        Point2D translatedBack = new Point2D.Double(projectedPoint.getX()+viewCenter.getX(),
+                projectedPoint.getY()+viewCenter.getY());
+        return translatedBack;
+    }
+    
+    /**
+     * Magnify the shape, without considering the Lens.
+     * @param shape the shape to magnify
+     * @return the transformed shape
+     */
+    public Shape magnify(Shape shape) {
+        return magnify(shape, 0);
+    }
+    public Shape magnify(Shape shape, float flatness) {
+        GeneralPath newPath = new GeneralPath();
+        float[] coords = new float[6];
+        PathIterator iterator = null;
+        if(flatness == 0) {
+            iterator = shape.getPathIterator(null);
+        } else {
+            iterator = shape.getPathIterator(null, flatness);
+        }
+        for( ;
+            iterator.isDone() == false;
+            iterator.next()) {
+            int type = iterator.currentSegment(coords);
+            switch(type) {
+            case PathIterator.SEG_MOVETO:
+                Point2D p = magnify(new Point2D.Float(coords[0], coords[1]));
+                newPath.moveTo((float)p.getX(), (float)p.getY());
+                break;
+                
+            case PathIterator.SEG_LINETO:
+                p = magnify(new Point2D.Float(coords[0], coords[1]));
+                newPath.lineTo((float)p.getX(), (float) p.getY());
+                break;
+                
+            case PathIterator.SEG_QUADTO:
+                p = magnify(new Point2D.Float(coords[0], coords[1]));
+                Point2D q = magnify(new Point2D.Float(coords[2], coords[3]));
+                newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY());
+                break;
+                
+            case PathIterator.SEG_CUBICTO:
+                p = magnify(new Point2D.Float(coords[0], coords[1]));
+                q = magnify(new Point2D.Float(coords[2], coords[3]));
+                Point2D r = magnify(new Point2D.Float(coords[4], coords[5]));
+                newPath.curveTo((float)p.getX(), (float)p.getY(), 
+                        (float)q.getX(), (float)q.getY(),
+                        (float)r.getX(), (float)r.getY());
+                break;
+                
+            case PathIterator.SEG_CLOSE:
+                newPath.closePath();
+                break;
+                    
+            }
+        }
+        return newPath;
+    }
+
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/ShapeFlatnessTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/ShapeFlatnessTransformer.java
new file mode 100644
index 0000000..2d81e79
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/ShapeFlatnessTransformer.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Apr 16, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform.shape;
+
+import java.awt.Shape;
+
+/**
+ * Provides methods to map points from one coordinate system to
+ * another: graph to screen and screen to graph.
+ * The flatness parameter is used to break a curved shape into
+ * smaller segments in order to perform a more detailed
+ * transformation.
+ * 
+ * @author Tom Nelson 
+ */
+public interface  ShapeFlatnessTransformer extends ShapeTransformer {
+    
+    /**
+     * map a shape from graph coordinate system to the
+     * screen coordinate system
+     * @param shape the shape to be transformed
+     * @param flatness used to break the supplied shape into segments
+     * @return a GeneralPath (Shape) representing the screen points of the shape
+     */
+    Shape transform(Shape shape, float flatness);
+    
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/ShapeTransformer.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/ShapeTransformer.java
new file mode 100644
index 0000000..b90df28
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/ShapeTransformer.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Apr 16, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform.shape;
+
+import java.awt.Shape;
+
+import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer;
+
+/**
+ * Provides methods to map points from one coordinate system to
+ * another: graph to screen and screen to graph.
+ * 
+ * @author Tom Nelson 
+ */
+public interface ShapeTransformer extends BidirectionalTransformer {
+    
+    /**
+     * map a shape from graph coordinate system to the
+     * screen coordinate system
+     * @param shape the Shape to transform
+     * @return a GeneralPath (Shape) representing the screen points of the shape
+     */
+    Shape transform(Shape shape);
+    
+    Shape inverseTransform(Shape shape);
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/TransformingFlatnessGraphics.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/TransformingFlatnessGraphics.java
new file mode 100644
index 0000000..c403aa2
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/TransformingFlatnessGraphics.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 11, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform.shape;
+
+import java.awt.Graphics2D;
+import java.awt.Shape;
+
+import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer;
+import edu.uci.ics.jung.visualization.transform.HyperbolicTransformer;
+
+
+/**
+ * subclassed to pass certain operations thru the Function
+ * before the base class method is applied
+ * This is useful when you want to apply non-affine transformations
+ * to the Graphics2D used to draw elements of the graph.
+ * 
+ * @author Tom Nelson 
+ *
+ *
+ */
+public class TransformingFlatnessGraphics extends TransformingGraphics {
+    
+	float flatness = 0;
+
+    public TransformingFlatnessGraphics(BidirectionalTransformer transformer) {
+        this(transformer, null);
+    }
+    
+    public TransformingFlatnessGraphics(BidirectionalTransformer transformer, Graphics2D delegate) {
+        super(transformer, delegate);
+    }
+    
+    public void draw(Shape s, float flatness) {
+        Shape shape = null;
+        if(transformer instanceof ShapeFlatnessTransformer) {
+            shape = ((ShapeFlatnessTransformer)transformer).transform(s, flatness);
+        } else {
+            shape = ((ShapeTransformer)transformer).transform(s);
+        }
+        delegate.draw(shape);
+        
+    }
+    
+    public void fill(Shape s, float flatness) {
+        Shape shape = null;
+        if(transformer instanceof HyperbolicTransformer) {
+            shape = ((HyperbolicShapeTransformer)transformer).transform(s, flatness);
+        } else {
+            shape = ((ShapeTransformer)transformer).transform(s);
+        }
+        delegate.fill(shape);
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/TransformingGraphics.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/TransformingGraphics.java
new file mode 100644
index 0000000..3609a22
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/TransformingGraphics.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Jul 11, 2005
+ */
+
+package edu.uci.ics.jung.visualization.transform.shape;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.ImageObserver;
+
+import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer;
+
+
+/**
+ * subclassed to pass certain operations thru the Function
+ * before the base class method is applied
+ * This is useful when you want to apply non-affine transformations
+ * to the Graphics2D used to draw elements of the graph.
+ * 
+ * @author Tom Nelson 
+ *
+ *
+ */
+public class TransformingGraphics extends GraphicsDecorator {
+    
+    /**
+     * the Function to apply
+     */
+    protected BidirectionalTransformer transformer;
+    
+    public TransformingGraphics(BidirectionalTransformer transformer) {
+        this(transformer, null);
+    }
+    
+    public TransformingGraphics(BidirectionalTransformer Function, Graphics2D delegate) {
+        super(delegate);
+        this.transformer = Function;
+    }
+    
+    /**
+     * @return Returns the Function.
+     */
+    public BidirectionalTransformer getTransformer() {
+        return transformer;
+    }
+    
+    /**
+     * @param Function The Function to set.
+     */
+    public void setTransformer(BidirectionalTransformer Function) {
+        this.transformer = Function;
+    }
+    
+    /**
+     * transform the shape before letting the delegate draw it
+     */
+    public void draw(Shape s) {
+        Shape shape = ((ShapeTransformer)transformer).transform(s);
+        delegate.draw(shape);
+    }
+    
+    public void draw(Shape s, float flatness) {
+        Shape shape = null;
+        if(transformer instanceof ShapeFlatnessTransformer) {
+            shape = ((ShapeFlatnessTransformer)transformer).transform(s, flatness);
+        } else {
+            shape = ((ShapeTransformer)transformer).transform(s);
+        }
+        delegate.draw(shape);
+        
+    }
+    
+    /**
+     * transform the shape before letting the delegate fill it
+     */
+    public void fill(Shape s) {
+        Shape shape = ((ShapeTransformer)transformer).transform(s);
+        delegate.fill(shape);
+    }
+    
+    public void fill(Shape s, float flatness) {
+        Shape shape = null;
+        if(transformer instanceof ShapeFlatnessTransformer) {
+            shape = ((ShapeFlatnessTransformer)transformer).transform(s, flatness);
+        } else {
+            shape = ((ShapeTransformer)transformer).transform(s);
+        }
+        delegate.fill(shape);
+    }
+    
+    public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
+    	Image image = null;
+        if(transformer instanceof ShapeFlatnessTransformer) {
+        	Rectangle2D r = new Rectangle2D.Double(x,y,img.getWidth(observer),img.getHeight(observer));
+        	Rectangle2D s = ((ShapeTransformer)transformer).transform(r).getBounds2D();
+        	image = img.getScaledInstance((int)s.getWidth(), (int)s.getHeight(), Image.SCALE_SMOOTH);
+        	x = (int) s.getMinX();
+        	y = (int) s.getMinY();
+        } else {
+            image = img;
+        }
+         return delegate.drawImage(image, x, y, observer);
+    }
+
+    public boolean drawImage(Image img, AffineTransform at, ImageObserver observer) {
+    	Image image = null;
+    	int x = (int)at.getTranslateX();
+    	int y = (int)at.getTranslateY();
+        if(transformer instanceof ShapeFlatnessTransformer) {
+        	Rectangle2D r = new Rectangle2D.Double(x,y,img.getWidth(observer),img.getHeight(observer));
+        	Rectangle2D s = ((ShapeTransformer)transformer).transform(r).getBounds2D();
+        	image = img.getScaledInstance((int)s.getWidth(), (int)s.getHeight(), Image.SCALE_SMOOTH);
+        	x = (int) s.getMinX();
+        	y = (int) s.getMinY();
+        	at.setToTranslation(s.getMinX(), s.getMinY());
+        } else {
+            image = img;
+        }
+         return delegate.drawImage(image, at, observer);
+    }
+
+    /**
+     * transform the shape before letting the delegate apply 'hit'
+     * with it
+     */
+    public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
+        Shape shape = ((ShapeTransformer)transformer).transform(s);
+        return delegate.hit(rect, shape, onStroke);
+    }
+    
+    public Graphics create() {
+        return delegate.create();
+    }
+    
+    public void dispose() {
+        delegate.dispose();
+    }
+    
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/ViewLensSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/ViewLensSupport.java
new file mode 100644
index 0000000..bb2acb1
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/ViewLensSupport.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ */
+package edu.uci.ics.jung.visualization.transform.shape;
+
+import java.awt.Dimension;
+
+import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
+import edu.uci.ics.jung.visualization.Layer;
+import edu.uci.ics.jung.visualization.RenderContext;
+import edu.uci.ics.jung.visualization.VisualizationViewer;
+import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
+import edu.uci.ics.jung.visualization.picking.ViewLensShapePickSupport;
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+import edu.uci.ics.jung.visualization.renderers.ReshapingEdgeRenderer;
+import edu.uci.ics.jung.visualization.transform.AbstractLensSupport;
+import edu.uci.ics.jung.visualization.transform.LensSupport;
+import edu.uci.ics.jung.visualization.transform.LensTransformer;
+
+/**
+ * Uses a LensTransformer to use in the view
+ * transform. This one will distort Vertex shapes.
+ * 
+ * @author Tom Nelson 
+ *
+ *
+ */
+public class ViewLensSupport<V,E> extends AbstractLensSupport<V,E>
+    implements LensSupport {
+    
+    protected RenderContext<V,E> renderContext;
+    protected GraphicsDecorator lensGraphicsDecorator;
+    protected GraphicsDecorator savedGraphicsDecorator;
+    protected GraphElementAccessor<V,E> pickSupport;
+    protected Renderer.Edge<V,E> savedEdgeRenderer;
+    protected Renderer.Edge<V,E> reshapingEdgeRenderer;
+
+    public ViewLensSupport(VisualizationViewer<V,E> vv, 
+    		LensTransformer lensTransformer,
+            ModalGraphMouse lensGraphMouse) {
+        super(vv, lensGraphMouse);
+        this.renderContext = vv.getRenderContext();
+        this.pickSupport = renderContext.getPickSupport();
+        this.savedGraphicsDecorator = renderContext.getGraphicsContext();
+        this.lensTransformer = lensTransformer;
+        Dimension d = vv.getSize();
+        lensTransformer.setViewRadius(d.width/5);
+        this.lensGraphicsDecorator = new TransformingFlatnessGraphics(lensTransformer);
+        this.savedEdgeRenderer = vv.getRenderer().getEdgeRenderer();
+        this.reshapingEdgeRenderer = new ReshapingEdgeRenderer<V,E>();
+        this.reshapingEdgeRenderer.setEdgeArrowRenderingSupport(savedEdgeRenderer.getEdgeArrowRenderingSupport());
+
+    }
+    public void activate() {
+    	lensTransformer.setDelegate(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW));
+        if(lens == null) {
+            lens = new Lens(lensTransformer);
+        }
+        if(lensControls == null) {
+            lensControls = new LensControls(lensTransformer);
+        }
+        renderContext.setPickSupport(new ViewLensShapePickSupport<V,E>(vv));
+        lensTransformer.setDelegate(vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW));
+        vv.getRenderContext().getMultiLayerTransformer().setTransformer(Layer.VIEW, lensTransformer);
+        this.renderContext.setGraphicsContext(lensGraphicsDecorator);
+        vv.getRenderer().setEdgeRenderer(reshapingEdgeRenderer);
+        vv.prependPreRenderPaintable(lens);
+        vv.addPostRenderPaintable(lensControls);
+        vv.setGraphMouse(lensGraphMouse);
+        vv.setToolTipText(instructions);
+        vv.repaint();
+    }
+
+    public void deactivate() {
+//    	savedViewTransformer.setTransform(lensTransformer.getDelegate().getTransform());
+//        vv.setViewTransformer(savedViewTransformer);
+    	renderContext.setPickSupport(pickSupport);
+        vv.getRenderContext().getMultiLayerTransformer().setTransformer(Layer.VIEW, lensTransformer.getDelegate());
+        vv.removePreRenderPaintable(lens);
+        vv.removePostRenderPaintable(lensControls);
+        this.renderContext.setGraphicsContext(savedGraphicsDecorator);
+        vv.setRenderContext(renderContext);
+        vv.setToolTipText(defaultToolTipText);
+        vv.setGraphMouse(graphMouse);
+        vv.getRenderer().setEdgeRenderer(savedEdgeRenderer);
+        vv.repaint();
+    }
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/package.html b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/package.html
new file mode 100644
index 0000000..f56768e
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/transform/shape/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Visualization mechanisms related to transformation of graph element shapes.
+
+</body>
+</html>
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/Animator.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/Animator.java
new file mode 100644
index 0000000..f31c3cf
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/Animator.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * 
+ */
+package edu.uci.ics.jung.visualization.util;
+
+import edu.uci.ics.jung.algorithms.util.IterativeContext;
+
+/**
+ * 
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ */
+public class Animator implements Runnable {
+	
+	protected IterativeContext process;
+	protected boolean stop;
+	protected Thread thread;
+	
+	/**
+	 * how long the relaxer thread pauses between iteration loops.
+	 */
+	protected long sleepTime = 10L;
+
+	
+	public Animator(IterativeContext process) {
+		this(process, 10L);
+	}
+
+	public Animator(IterativeContext process, long sleepTime) {
+		this.process = process;
+		this.sleepTime = sleepTime;
+	}
+
+	/**
+	 * @return the relaxer thread sleep time
+	 */
+	public long getSleepTime() {
+		return sleepTime;
+	}
+
+	/**
+	 * @param sleepTime the relaxer thread sleep time to set
+	 */
+	public void setSleepTime(long sleepTime) {
+		this.sleepTime = sleepTime;
+	}
+	
+	public void start() {
+		// in case its running
+		stop();
+		stop = false;
+		thread = new Thread(this);
+		thread.setPriority(Thread.MIN_PRIORITY);
+		thread.start();
+	}
+	
+	public synchronized void stop() {
+		stop = true;
+	}
+
+	public void run() {
+		while (!process.done() && !stop) {
+
+			process.step();
+
+			if (stop)
+				return;
+
+			try {
+				Thread.sleep(sleepTime);
+			} catch (InterruptedException ie) {
+			}
+		}
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/ArrowFactory.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/ArrowFactory.java
new file mode 100644
index 0000000..beadc1c
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/ArrowFactory.java
@@ -0,0 +1,65 @@
+/*
+ * Created on Oct 19, 2004
+ *
+ * Copyright (c) 2004, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.visualization.util;
+
+import java.awt.geom.GeneralPath;
+
+/**
+ * A utility class for creating arrowhead shapes.
+ * 
+ * @author Joshua O'Madadhain
+ */
+public class ArrowFactory
+{
+    /**
+     * Returns an arrowhead in the shape of a simple isosceles triangle
+     * with the specified base and height measurements.  It is placed
+     * with the vertical axis along the negative x-axis, with its base
+     * centered on (0,0).
+     * 
+     * @param base the width of the arrow's base
+     * @param height the arrow's height
+     * @return a path in the form of an isosceles triangle with dimensions {@code (base, height)}
+     */
+    public static GeneralPath getWedgeArrow(float base, float height)
+    {
+        GeneralPath arrow = new GeneralPath();
+        arrow.moveTo(0,0);
+        arrow.lineTo( - height, base/2.0f);
+        arrow.lineTo( - height, -base/2.0f);
+        arrow.lineTo( 0, 0 );
+        return arrow;
+    }
+
+    /**
+     * Returns an arrowhead in the shape of an isosceles triangle
+     * with an isoceles-triangle notch taken out of the base,
+     * with the specified base and height measurements.  It is placed
+     * with the vertical axis along the negative x-axis, with its base
+     * centered on (0,0).
+     * 
+     * @param base the width of the arrow's base
+     * @param height the arrow's height
+     * @param notch_height the height of the arrow's notch
+     * @return a path in the form of a notched isosceles triangle
+     */
+    public static GeneralPath getNotchedArrow(float base, float height, float notch_height)
+    {
+        GeneralPath arrow = new GeneralPath();
+        arrow.moveTo(0,0);
+        arrow.lineTo(-height, base/2.0f);
+        arrow.lineTo(-(height - notch_height), 0);
+        arrow.lineTo(-height, -base/2.0f);
+        arrow.lineTo(0,0);
+        return arrow;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/Caching.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/Caching.java
new file mode 100644
index 0000000..d74568a
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/Caching.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * 
+ */
+package edu.uci.ics.jung.visualization.util;
+
+/**
+ * Interface to provide external controls to an
+ * implementing class that manages a cache.
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ */
+public interface Caching {
+	
+	/**
+	 * ititialize resources for a cache
+	 *
+	 */
+	void init();
+	
+	/**
+	 * clear cache
+	 *
+	 */
+	void clear();
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/ChangeEventSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/ChangeEventSupport.java
new file mode 100644
index 0000000..7efbb2f
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/ChangeEventSupport.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 18, 2005
+ */
+
+package edu.uci.ics.jung.visualization.util;
+
+import javax.swing.event.ChangeListener;
+
+/**
+ * the implementing class provides support for ChangeEvents.
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ */
+public interface ChangeEventSupport {
+
+    void addChangeListener(ChangeListener l);
+
+    /**
+     * Removes a ChangeListener.
+     * @param l the listener to be removed
+     */
+    void removeChangeListener(ChangeListener l);
+
+    /**
+     * Returns an array of all the <code>ChangeListener</code>s added
+     * with addChangeListener().
+     *
+     * @return all of the <code>ChangeListener</code>s added or an empty
+     *         array if no listeners have been added
+     */
+    ChangeListener[] getChangeListeners();
+    
+    void fireStateChanged();
+
+}
\ No newline at end of file
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/DefaultChangeEventSupport.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/DefaultChangeEventSupport.java
new file mode 100644
index 0000000..02b143b
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/DefaultChangeEventSupport.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Aug 18, 2005
+ */
+
+package edu.uci.ics.jung.visualization.util;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.EventListenerList;
+
+/**
+ * Basic implementation of ChangeEventSupport, using
+ * standard jdk classes
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ */
+public class DefaultChangeEventSupport implements ChangeEventSupport {
+    
+    Object eventSource;
+    /**
+     * holds the registered listeners
+     */
+    protected EventListenerList listenerList = new EventListenerList();
+
+    /**
+     * Only one <code>ChangeEvent</code> is needed
+     * instance since the
+     * event's only state is the source property.  The source of events
+     * generated is always "this".
+     */
+    protected transient ChangeEvent changeEvent;
+    
+    public DefaultChangeEventSupport(Object eventSource) {
+        this.eventSource = eventSource;
+    }
+
+    public void addChangeListener(ChangeListener l) {
+        listenerList.add(ChangeListener.class, l);
+    }
+    
+    public void removeChangeListener(ChangeListener l) {
+        listenerList.remove(ChangeListener.class, l);
+    }
+    
+    public ChangeListener[] getChangeListeners() {
+        return listenerList.getListeners(ChangeListener.class);
+    }
+
+    /**
+     * Notifies all listeners that have registered interest for
+     * notification on this event type.  The event instance 
+     * is lazily created.
+     * The primary listeners will be views that need to be repainted
+     * because of changes in this model instance
+     * @see EventListenerList
+     */
+    public void fireStateChanged() {
+        // Guaranteed to return a non-null array
+        Object[] listeners = listenerList.getListenerList();
+        // Process the listeners last to first, notifying
+        // those that are interested in this event
+        for (int i = listeners.length-2; i>=0; i-=2) {
+            if (listeners[i]==ChangeListener.class) {
+                // Lazily create the event:
+                if (changeEvent == null)
+                    changeEvent = new ChangeEvent(eventSource);
+                ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
+            }          
+        }
+    }   
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/GeneralPathAsString.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/GeneralPathAsString.java
new file mode 100644
index 0000000..78dfe42
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/GeneralPathAsString.java
@@ -0,0 +1,51 @@
+package edu.uci.ics.jung.visualization.util;
+
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+
+public class GeneralPathAsString {
+
+    public static String toString(GeneralPath newPath) {
+    	StringBuilder sb = new StringBuilder();
+        float[] coords = new float[6];
+        for(PathIterator iterator=newPath.getPathIterator(null);
+            iterator.isDone() == false;
+            iterator.next()) {
+            int type = iterator.currentSegment(coords);
+            switch(type) {
+            case PathIterator.SEG_MOVETO:
+                Point2D p = new Point2D.Float(coords[0], coords[1]);
+                sb.append("moveTo "+p+"--");
+                break;
+                
+            case PathIterator.SEG_LINETO:
+                p = new Point2D.Float(coords[0], coords[1]);
+                sb.append("lineTo "+p+"--");
+                break;
+                
+            case PathIterator.SEG_QUADTO:
+                p = new Point2D.Float(coords[0], coords[1]);
+                Point2D q = new Point2D.Float(coords[2], coords[3]);
+                sb.append("quadTo "+p+" controlled by "+q);
+                break;
+                
+            case PathIterator.SEG_CUBICTO:
+                p = new Point2D.Float(coords[0], coords[1]);
+                q = new Point2D.Float(coords[2], coords[3]);
+                Point2D r = new Point2D.Float(coords[4], coords[5]);
+                sb.append("cubeTo "+p+" controlled by "+q+","+r);
+
+                break;
+                
+            case PathIterator.SEG_CLOSE:
+                newPath.closePath();
+                sb.append("close");
+                break;
+                    
+            }
+        }
+        return sb.toString();
+    }
+
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/ImageShapeUtils.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/ImageShapeUtils.java
new file mode 100644
index 0000000..72e4cc4
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/ImageShapeUtils.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2015, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ * Created on Nov 7, 2015
+ */
+
+package edu.uci.ics.jung.visualization.util;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+import javax.imageio.ImageIO;
+
+import edu.uci.ics.jung.visualization.FourPassImageShaper;
+
+public class ImageShapeUtils {
+	
+    /**
+     * Given the fileName of an image, possibly with a transparent
+     * background, return the Shape of the opaque part of the image
+     * @param fileName name of the image, loaded from the classpath
+     * @return the Shape
+     */
+    public static Shape getShape(String fileName) {
+        return getShape(fileName, Integer.MAX_VALUE);
+    }
+    
+    /**
+     * Given the fileName of an image, possibly with a transparent
+     * background, return the Shape of the opaque part of the image
+     * @param fileName name of the image, loaded from the classpath
+     * @param max the maximum dimension of the traced shape
+     * @return the Shape
+     * 
+     * @see #getShape(Image, int)
+     */
+    public static Shape getShape(String fileName, int max) {
+        BufferedImage image = null;
+        try {
+            image = ImageIO.read(ImageShapeUtils.class.getResource(fileName));
+        } catch(IOException ex) {
+            ex.printStackTrace();
+        }
+        return getShape(image, max);
+    }
+    
+    /**
+     * Given an image, possibly with a transparent background, return
+     * the Shape of the opaque part of the image
+     * @param image the image whose shape is to be returned
+     * @return the Shape
+     */
+    public static Shape getShape(Image image) {
+        return getShape(image, Integer.MAX_VALUE);
+    }
+    public static Shape getShape(Image image, int max) {
+        BufferedImage bi = 
+            new BufferedImage(image.getWidth(null), image.getHeight(null), 
+                    BufferedImage.TYPE_INT_ARGB);
+        Graphics g = bi.createGraphics();
+        g.drawImage(image, 0, 0, null);
+        g.dispose();
+        return getShape(bi, max);
+    }
+    
+    /**
+     * Given an image, possibly with a transparent background, return
+     * the Shape of the opaque part of the image
+     * 
+     * If the image is larger than max in either direction, scale the
+     * image down to max-by-max, do the trace (on fewer points) then
+     * scale the resulting shape back up to the size of the original
+     * image.
+     * 
+     * @param image the image to trace
+     * @param max used to restrict number of points in the resulting shape
+     * @return the Shape
+     */
+    public static Shape getShape(BufferedImage image, int max) {
+        float width = image.getWidth();
+        float height = image.getHeight();
+        if(width > max || height > max) {
+            BufferedImage smaller = 
+                new BufferedImage(max, max, BufferedImage.TYPE_INT_ARGB);
+            Graphics g = smaller.createGraphics();
+            AffineTransform at = AffineTransform.getScaleInstance(max/width,max/height);
+            AffineTransform back = AffineTransform.getScaleInstance(width/max,height/max);
+            Graphics2D g2 = (Graphics2D)g;
+            g2.drawImage(image, at, null);
+            g2.dispose();
+            return back.createTransformedShape(getShape(smaller));
+        } else {
+            return FourPassImageShaper.getShape(image);
+        }
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/LabelWrapper.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/LabelWrapper.java
new file mode 100644
index 0000000..3a87f0c
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/LabelWrapper.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2005, The JUNG Authors
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ *
+ */
+package edu.uci.ics.jung.visualization.util;
+
+import com.google.common.base.Function;
+
+/**
+ * A utility to wrap long lines, creating html strings
+ * with line breaks at a settable max line length
+ * 
+ * @author Tom Nelson - tomnelson at dev.java.net
+ *
+ */
+public class LabelWrapper implements Function<String,String> {
+
+	int lineLength;
+	public static final String breaker = "<p>";
+	
+	/**
+	 * Create an instance with default line break length = 10
+	 *
+	 */
+	public LabelWrapper() {
+		this(10);
+	}
+	
+	/**
+	 * Create an instance with passed line break length
+	 * @param lineLength the max length for lines
+	 */
+	public LabelWrapper(int lineLength) {
+		this.lineLength = lineLength;
+	}
+
+	/**
+	 * call 'wrap' to transform the passed String
+	 */
+	public String apply(String str) {
+		if(str != null) {
+			return wrap(str);
+		} else {
+			return null;
+		}
+	}
+	
+	/**
+	 * line-wrap the passed String as an html string with
+	 * break Strings inserted.
+	 * 
+	 * @param str
+	 * @return
+	 */
+	private String wrap(String str) {
+		StringBuilder buf = new StringBuilder(str);
+		int len = lineLength;
+		while(len < buf.length()) {
+			int idx = buf.lastIndexOf(" ", len);
+			if(idx != -1) {
+				buf.replace(idx, idx+1, breaker);
+				len = idx + breaker.length() +lineLength;
+			} else {
+				buf.insert(len, breaker);
+				len += breaker.length() + lineLength;
+			}
+		}
+		buf.insert(0, "<html>");
+		return buf.toString();
+	}
+	
+	public static void main(String[] args) {
+		String[] lines = {
+				"This is a line with many short words that I will break into shorter lines.",
+				"thisisalinewithnobreakssowhoknowswhereitwillwrap",
+				"short line"
+		};
+		LabelWrapper w = new LabelWrapper(10);
+		for(int i=0; i<lines.length; i++) {
+			System.err.println("from "+lines[i]+" to "+w.wrap(lines[i]));
+		}
+	}
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/PredicatedParallelEdgeIndexFunction.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/PredicatedParallelEdgeIndexFunction.java
new file mode 100644
index 0000000..fbee157
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/PredicatedParallelEdgeIndexFunction.java
@@ -0,0 +1,152 @@
+/*
+ * Created on Sep 24, 2005
+ *
+ * Copyright (c) 2005, The JUNG Authors 
+ *
+ * All rights reserved.
+ *
+ * This software is open-source under the BSD license; see either
+ * "license.txt" or
+ * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ */
+package edu.uci.ics.jung.visualization.util;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.google.common.base.Predicate;
+
+import edu.uci.ics.jung.graph.Graph;
+import edu.uci.ics.jung.graph.util.EdgeIndexFunction;
+import edu.uci.ics.jung.graph.util.Pair;
+
+/**
+ * A class which creates and maintains indices for parallel edges.
+ * Edges are evaluated by a Predicate function and those that
+ * evaluate to true are excluded from computing a parallel offset
+ * 
+ * @author Tom Nelson
+ *
+ */
+public class PredicatedParallelEdgeIndexFunction<V,E> implements EdgeIndexFunction<V,E> {
+    protected Map<E, Integer> edge_index = new HashMap<E, Integer>();
+    protected Predicate<E> predicate;
+    
+	/**
+     * @param graph the graph with respect to which the index is calculated
+	 */
+    private PredicatedParallelEdgeIndexFunction() {
+    }
+    
+    public static <V,E> PredicatedParallelEdgeIndexFunction<V,E> getInstance() {
+        return new PredicatedParallelEdgeIndexFunction<V,E>();
+    }
+
+    /**
+     * Returns the index for the specified edge.
+     * Calculates the indices for <code>e</code> and for all edges parallel
+     * to <code>e</code>.
+     * @param graph the graph with respect to which the index is calculated
+     * @param e the edge whose index is to be calculated
+     * 
+     * @return the index of the edge with respect to this index function
+     */
+    public int getIndex(Graph<V, E> graph, E e) {
+    	
+    	if(predicate.apply(e)) {
+    		return 0;
+    	}
+        Integer index = edge_index.get(e);
+        if(index == null) {
+        	Pair<V> endpoints = graph.getEndpoints(e);
+        	V u = endpoints.getFirst();
+        	V v = endpoints.getSecond();
+        	if(u.equals(v)) {
+        		index = getIndex(graph, e, v);
+        	} else {
+        		index = getIndex(graph, e, u, v);
+        	}
+        }
+        return index.intValue();
+    }
+
+    protected int getIndex(Graph<V,E> graph, E e, V v, V u) {
+    	Collection<E> commonEdgeSet = new HashSet<E>(graph.getIncidentEdges(u));
+    	commonEdgeSet.retainAll(graph.getIncidentEdges(v));
+    	for(Iterator<E> iterator=commonEdgeSet.iterator(); iterator.hasNext(); ) {
+    		E edge = iterator.next();
+    		Pair<V> ep = graph.getEndpoints(edge);
+    		V first = ep.getFirst();
+    		V second = ep.getSecond();
+    		// remove loops
+    		if(first.equals(second) == true) {
+    			iterator.remove();
+    		}
+    		// remove edges in opposite direction
+    		if(first.equals(v) == false) {
+    			iterator.remove();
+    		}
+    	}
+    	int count=0;
+    	for(E other : commonEdgeSet) {
+    		if(e.equals(other) == false) {
+    			edge_index.put(other, count);
+    			count++;
+    		}
+    	}
+    	edge_index.put(e, count);
+    	return count;
+     }
+    
+    protected int getIndex(Graph<V,E> graph, E e, V v) {
+    	Collection<E> commonEdgeSet = new HashSet<E>();
+    	for(E another : graph.getIncidentEdges(v)) {
+    		V u = graph.getOpposite(v, another);
+    		if(u.equals(v)) {
+    			commonEdgeSet.add(another);
+    		}
+    	}
+    	int count=0;
+    	for(E other : commonEdgeSet) {
+    		if(e.equals(other) == false) {
+    			edge_index.put(other, count);
+    			count++;
+    		}
+    	}
+    	edge_index.put(e, count);
+    	return count;
+    }
+
+	public Predicate<E> getPredicate() {
+		return predicate;
+	}
+
+	public void setPredicate(Predicate<E> predicate) {
+		this.predicate = predicate;
+	}
+
+	/**
+     * Resets the indices for this edge and its parallel edges.
+     * Should be invoked when an edge parallel to <code>e</code>
+     * has been added or removed in this graph.
+     * @param graph the graph with respect to which the index is calculated
+     * @param e the edge whose indices are to be reset for {@code graph}
+     */
+    public void reset(Graph<V,E> graph, E e) {
+    	Pair<V> endpoints = graph.getEndpoints(e);
+        getIndex(graph, e, endpoints.getFirst());
+        getIndex(graph, e, endpoints.getFirst(), endpoints.getSecond());
+    }
+    
+    /**
+     * Clears all edge indices for all edges in all graphs.
+     * Does not recalculate the indices.
+     */
+    public void reset()
+    {
+        edge_index.clear();
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/VertexShapeFactory.java b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/VertexShapeFactory.java
new file mode 100644
index 0000000..aef9ec2
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/VertexShapeFactory.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2003, The JUNG Authors
+ * All rights reserved.
+ * 
+ * This software is open-source under the BSD license; see either "license.txt"
+ * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
+ * 
+ * Created on Jul 20, 2004
+ */
+package edu.uci.ics.jung.visualization.util;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+
+/**
+ * A utility class for generating <code>Shape</code>s for drawing vertices.  
+ * The available shapes include rectangles, rounded rectangles, ellipses,
+ * regular polygons, and regular stars.  The dimensions of the requested 
+ * shapes are defined by the specified vertex size function (specified by
+ * a {@code Function<? super V, Integer>}) and vertex aspect ratio function 
+ * (specified by a {@code Function<? super V, Float>}) implementations: the width
+ * of the bounding box of the shape is given by the vertex size, and the
+ * height is given by the size multiplied by the vertex's aspect ratio.
+ *  
+ * @author Joshua O'Madadhain
+ */
+public class VertexShapeFactory<V>
+{
+    protected Function<? super V, Integer> vsf;
+    protected Function<? super V, Float> varf;
+    
+    /**
+     * Creates an instance with the specified vertex size and aspect ratio functions.
+     * 
+     * @param vsf provides a size (width) for each vertex
+     * @param varf provides a height/width ratio for each vertex
+     */
+    public VertexShapeFactory(Function<? super V,Integer> vsf, Function<? super V,Float> varf)
+    {
+        this.vsf = vsf;
+        this.varf = varf;
+    }
+    
+    /**
+     * Creates a <code>VertexShapeFactory</code> with a constant size of
+     * 10 and a constant aspect ratio of 1.
+     */
+	public VertexShapeFactory()
+    {
+        this(Functions.constant(10), 
+            Functions.constant(1.0f));
+    }
+    
+    private static final Rectangle2D theRectangle = new Rectangle2D.Float();
+
+    /**
+     * Returns a <code>Rectangle2D</code> whose width and 
+     * height are defined by this instance's size and
+     * aspect ratio functions for this vertex.
+     * 
+     * @param v the vertex for which the shape will be drawn
+     * @return a rectangle for this vertex
+     */
+    public Rectangle2D getRectangle(V v)
+    {
+        float width = vsf.apply(v);
+        float height = width * varf.apply(v);
+        float h_offset = -(width / 2);
+        float v_offset = -(height / 2);
+        theRectangle.setFrame(h_offset, v_offset, width, height);
+        return theRectangle;
+    }
+
+    private static final Ellipse2D theEllipse = new Ellipse2D.Float();
+
+    /**
+     * Returns a <code>Ellipse2D</code> whose width and 
+     * height are defined by this instance's size and
+     * aspect ratio functions for this vertex.
+     * 
+     * @param v the vertex for which the shape will be drawn
+     * @return an ellipse for this vertex
+     */
+    public Ellipse2D getEllipse(V v)
+    {
+        theEllipse.setFrame(getRectangle(v));
+        return theEllipse;
+    }
+    
+    private static final RoundRectangle2D theRoundRectangle =
+        new RoundRectangle2D.Float();
+    /**
+     * Returns a <code>RoundRectangle2D</code> whose width and 
+     * height are defined by this instance's size and
+     * aspect ratio functions for this vertex.  The arc size is
+     * set to be half the minimum of the height and width of the frame.
+     * 
+     * @param v the vertex for which the shape will be drawn
+     * @return an round rectangle for this vertex
+     */
+    public RoundRectangle2D getRoundRectangle(V v)
+    {
+        Rectangle2D frame = getRectangle(v);
+        float arc_size = (float)Math.min(frame.getHeight(), frame.getWidth()) / 2;
+        theRoundRectangle.setRoundRect(frame.getX(), frame.getY(),
+                frame.getWidth(), frame.getHeight(), arc_size, arc_size);
+        return theRoundRectangle;
+    }
+    
+    private static final GeneralPath thePolygon = new GeneralPath();
+
+    /**
+     * Returns a regular <code>num_sides</code>-sided 
+     * <code>Polygon</code> whose bounding 
+     * box's width and height are defined by this instance's size and
+     * aspect ratio functions for this vertex.
+     * 
+     * @param v the vertex for which the shape will be drawn
+     * @param num_sides the number of sides of the polygon; must be ≥ 3.
+     * @return a regular polygon for this vertex
+     */
+    public Shape getRegularPolygon(V v, int num_sides)
+    {
+        if (num_sides < 3)
+            throw new IllegalArgumentException("Number of sides must be >= 3");
+        Rectangle2D frame = getRectangle(v);
+        float width = (float)frame.getWidth();
+        float height = (float)frame.getHeight();
+        
+        // generate coordinates
+        double angle = 0;
+        thePolygon.reset();
+        thePolygon.moveTo(0,0);
+        thePolygon.lineTo(width, 0);
+        double theta = (2 * Math.PI) / num_sides; 
+        for (int i = 2; i < num_sides; i++)
+        {
+            angle -= theta; 
+            float delta_x = (float) (width * Math.cos(angle));
+            float delta_y = (float) (width * Math.sin(angle));
+            Point2D prev = thePolygon.getCurrentPoint();
+            thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y);
+        }
+        thePolygon.closePath();
+        
+        // scale polygon to be right size, translate to center at (0,0)
+        Rectangle2D r = thePolygon.getBounds2D();
+        double scale_x = width / r.getWidth();
+        double scale_y = height / r.getHeight();
+        float translationX = (float) (r.getMinX() + r.getWidth()/2);
+        float translationY = (float) (r.getMinY() + r.getHeight()/2);
+
+        AffineTransform at = AffineTransform.getScaleInstance(scale_x, scale_y);
+        at.translate(-translationX, -translationY);
+
+        Shape shape = at.createTransformedShape(thePolygon);
+        return shape;
+    }
+    
+    /**
+     * Returns a regular <code>Polygon</code> of <code>num_points</code>
+     * points whose bounding 
+     * box's width and height are defined by this instance's size and
+     * aspect ratio functions for this vertex.
+     * 
+     * @param v the vertex for which the shape will be drawn
+     * @param num_points the number of points of the polygon; must be ≥ 5.
+     * @return an star shape for this vertex
+     */
+    public Shape getRegularStar(V v, int num_points)
+    {
+        if (num_points < 5)
+            throw new IllegalArgumentException("Number of sides must be >= 5");
+        Rectangle2D frame = getRectangle(v);
+        float width = (float) frame.getWidth();
+        float height = (float) frame.getHeight();
+        
+        // generate coordinates
+        double theta = (2 * Math.PI) / num_points;
+        double angle = -theta/2;
+        thePolygon.reset();
+        thePolygon.moveTo(0,0);
+        float delta_x = width * (float)Math.cos(angle);
+        float delta_y = width * (float)Math.sin(angle);
+        Point2D prev = thePolygon.getCurrentPoint();
+        thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y);
+        for (int i = 1; i < num_points; i++)
+        {
+            angle += theta; 
+            delta_x = width * (float)Math.cos(angle);
+            delta_y = width * (float)Math.sin(angle);
+            prev = thePolygon.getCurrentPoint();
+            thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y);
+            angle -= theta*2; 
+            delta_x = width * (float)Math.cos(angle);
+            delta_y = width * (float)Math.sin(angle);
+            prev = thePolygon.getCurrentPoint();
+            thePolygon.lineTo((float)prev.getX() + delta_x, (float)prev.getY() + delta_y);
+        }
+        thePolygon.closePath();
+        
+        // scale polygon to be right size, translate to center at (0,0)
+        Rectangle2D r = thePolygon.getBounds2D();
+        double scale_x = width / r.getWidth();
+        double scale_y = height / r.getHeight();
+
+        float translationX = (float) (r.getMinX() + r.getWidth()/2);
+        float translationY = (float) (r.getMinY() + r.getHeight()/2);
+        
+        AffineTransform at = AffineTransform.getScaleInstance(scale_x, scale_y);
+        at.translate(-translationX, -translationY);
+
+        Shape shape = at.createTransformedShape(thePolygon);
+        return shape;
+    }
+}
diff --git a/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/package.html b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/package.html
new file mode 100644
index 0000000..c2769c0
--- /dev/null
+++ b/jung-visualization/src/main/java/edu/uci/ics/jung/visualization/util/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html
+
+ Copyright (c) 2003-2015 The JUNG Authors. 
+ All Rights Reserved. 
+ 
+ License text: https://github.com/jrtom/jung/blob/master/LICENSE
+-->
+</head>
+<body>
+
+<p>Utilities for graph visualization.
+
+</body>
+</html>
diff --git a/jung-visualization/src/site/site.xml b/jung-visualization/src/site/site.xml
new file mode 100644
index 0000000..4f25bdf
--- /dev/null
+++ b/jung-visualization/src/site/site.xml
@@ -0,0 +1,14 @@
+<project name="${project.name}">
+  <bannerLeft>
+    <name>${project.name}</name>
+  </bannerLeft>
+  <body>
+    <links>
+      <item name="${project.name}" href="${project.url}"/>
+    </links>
+    <menu ref="parent" />
+    <menu ref="modules" />
+    <menu ref="reports" />
+  </body>
+</project>
+
diff --git a/jung-visualization/src/test/java/edu/uci/ics/jung/visualization/BasicVisualizationServerTest.java b/jung-visualization/src/test/java/edu/uci/ics/jung/visualization/BasicVisualizationServerTest.java
new file mode 100644
index 0000000..1e5963d
--- /dev/null
+++ b/jung-visualization/src/test/java/edu/uci/ics/jung/visualization/BasicVisualizationServerTest.java
@@ -0,0 +1,23 @@
+package edu.uci.ics.jung.visualization;
+
+import edu.uci.ics.jung.algorithms.layout.CircleLayout;
+import edu.uci.ics.jung.graph.SparseGraph;
+import edu.uci.ics.jung.visualization.picking.PickedState;
+import junit.framework.TestCase;
+
+public class BasicVisualizationServerTest extends TestCase {
+
+  /*
+   * Previously, a bug was introduced where the RenderContext in BasicVisualizationServer was reassigned, resulting
+   * in data like pickedVertexState to be lost.
+   */
+  public void testRenderContextNotOverridden() {
+    SparseGraph<Object, Object> graph = new SparseGraph<Object, Object>();
+    CircleLayout<Object, Object> layout = new CircleLayout<Object, Object>(graph);
+
+    BasicVisualizationServer<Object, Object> server = new BasicVisualizationServer<Object, Object>(layout);
+
+    PickedState<Object> pickedVertexState = server.getRenderContext().getPickedVertexState();
+    assertNotNull(pickedVertexState);
+  }
+}
diff --git a/jung-visualization/src/test/java/edu/uci/ics/jung/visualization/TestImageShaper.java b/jung-visualization/src/test/java/edu/uci/ics/jung/visualization/TestImageShaper.java
new file mode 100644
index 0000000..0b82411
--- /dev/null
+++ b/jung-visualization/src/test/java/edu/uci/ics/jung/visualization/TestImageShaper.java
@@ -0,0 +1,55 @@
+package edu.uci.ics.jung.visualization;
+
+import java.awt.Shape;
+import java.awt.geom.PathIterator;
+import java.awt.image.BufferedImage;
+
+import edu.uci.ics.jung.visualization.util.ImageShapeUtils;
+import junit.framework.TestCase;
+
+public class TestImageShaper extends TestCase {
+	
+	BufferedImage image;
+	
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+		int width = 6;
+		int height = 5;
+		image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+		for(int i=0; i<height; i++) {
+			for(int j=0; j<width; j++) {
+				image.setRGB(j, i, 0x00000000);
+//				System.err.println("["+j+","+i+"] = "+image.getRGB(j, i));
+//				if((image.getRGB(j,i) & 0xff000000) != 0) {
+//					System.err.println("got an opaque point at ["+j+","+i+"]");
+//				}
+			}
+		}
+		image.setRGB(3, 1, 0xffffffff);
+		image.setRGB(4, 1, 0xffffffff);
+		image.setRGB(2, 2, 0xffffffff);
+		image.setRGB(4, 2, 0xffffffff);
+		image.setRGB(1, 3, 0xffffffff);
+		image.setRGB(2, 3, 0xffffffff);
+		image.setRGB(3, 3, 0xffffffff);
+		image.setRGB(4, 3, 0xffffffff);
+	}
+
+	public void testShaper() {
+		Shape shape = ImageShapeUtils.getShape(image, 30);
+//		System.err.println("complete shape = "+shape);
+		float[] seg = new float[6];
+		for (PathIterator i = shape.getPathIterator(null, 1); !i.isDone(); i
+				.next()) {
+			int ret = i.currentSegment(seg);
+//			if (ret == PathIterator.SEG_MOVETO) {
+//				System.err.println("move to "+seg[0]+","+seg[1]);
+//			} else if (ret == PathIterator.SEG_LINETO) {
+//				System.err.println("line to "+seg[0]+","+seg[1]);
+//			} else if(ret == PathIterator.SEG_CLOSE) {
+//				System.err.println("done");
+//			}
+		}
+	}
+}
diff --git a/jung-visualization/src/test/java/edu/uci/ics/jung/visualization/control/TestCrossoverScalingControl.java b/jung-visualization/src/test/java/edu/uci/ics/jung/visualization/control/TestCrossoverScalingControl.java
new file mode 100644
index 0000000..84669bf
--- /dev/null
+++ b/jung-visualization/src/test/java/edu/uci/ics/jung/visualization/control/TestCrossoverScalingControl.java
@@ -0,0 +1,76 @@
+package edu.uci.ics.jung.visualization.control;
+
+import java.awt.geom.Point2D;
+
+import junit.framework.TestCase;
+import edu.uci.ics.jung.algorithms.layout.FRLayout;
+import edu.uci.ics.jung.graph.SparseGraph;
+import edu.uci.ics.jung.visualization.BasicVisualizationServer;
+import edu.uci.ics.jung.visualization.VisualizationServer;
+
+public class TestCrossoverScalingControl extends TestCase {
+
+	CrossoverScalingControl sc;
+	VisualizationServer<?, ?> vv;
+	
+	float crossover;
+	float scale;
+	
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	@Override
+    public void setUp() {
+		sc = new CrossoverScalingControl();
+		vv = new BasicVisualizationServer(new FRLayout(new SparseGraph()));
+	}
+	public void testCrossover() {
+		crossover = 2.0f;
+		scale = .5f;
+		sc.setCrossover(crossover);
+		sc.scale(vv, scale, new Point2D.Double());
+//		System.err.println("crossover="+crossover);
+//		System.err.println("scale="+scale);
+//		System.err.println("layout scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale());
+//		System.err.println("view scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getScale());
+	}
+	public void testCrossover2() {
+		crossover = 2.0f;
+		scale = 1.5f;
+		sc.setCrossover(crossover);
+		sc.scale(vv, scale, new Point2D.Double());
+//		System.err.println("crossover="+crossover);
+//		System.err.println("scale="+scale);
+//		System.err.println("layout scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale());
+//		System.err.println("view scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getScale());
+		
+	}
+	public void testCrossover3() {
+		crossover = 2.0f;
+		scale = 2.5f;
+		sc.setCrossover(crossover);
+		sc.scale(vv, scale, new Point2D.Double());
+//		System.err.println("crossover="+crossover);
+//		System.err.println("scale="+scale);
+//		System.err.println("layout scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale());
+//		System.err.println("view scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getScale());
+	}
+	public void testCrossover4() {
+		crossover = 0.5f;
+		scale = 2.5f;
+		sc.setCrossover(crossover);
+		sc.scale(vv, scale, new Point2D.Double());
+//		System.err.println("crossover="+crossover);
+//		System.err.println("scale="+scale);
+//		System.err.println("layout scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale());
+//		System.err.println("view scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getScale());
+	}
+	public void testCrossover5() {
+		crossover = 0.5f;
+		scale = .3f;
+		sc.setCrossover(crossover);
+		sc.scale(vv, scale, new Point2D.Double());
+//		System.err.println("crossover="+crossover);
+//		System.err.println("scale="+scale);
+//		System.err.println("layout scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale());
+//		System.err.println("view scale = "+vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getScale());
+	}
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..d404b7f
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,264 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>net.sf.jung</groupId>
+  <artifactId>jung-parent</artifactId>
+  <version>2.1.1</version>
+  <packaging>pom</packaging>
+  <name>JUNG (parent metadata project)</name>
+  <url>http://jrtom.github.io/jung/</url>
+  <licenses>
+    <license>
+      <name>The BSD License</name>
+      <url>https://github.com/jrtom/jung/blob/master/LICENSE</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+  <scm>
+    <url>https://github.com/jrtom/jung</url>
+    <connection>scm:git:git://github.com/jrtom/jung.git</connection>
+    <developerConnection>scm:git:git://github.com/jrtom/jung.git</developerConnection>
+  </scm>
+  <description>
+    JUNG the Java Universal Network/Graph Framework--is a software
+    library that provides a common and extendible language for the
+    modeling, analysis, and visualization of data that can be
+    represented as a graph or network. It is written in Java, which
+    allows JUNG-based applications to make use of the extensive
+    built-in capabilities of the Java API, as well as those of other
+    existing third-party Java libraries. The JUNG architecture is
+    designed to support a variety of representations of entities and
+    their relations, such as directed and undirected graphs,
+    multi-modal graphs, graphs with parallel edges, and hypergraphs.
+    It provides a mechanism for annotating graphs, entities, and
+    relations with metadata. This facilitates the creation of
+    analytic tools for complex data sets that can examine the
+    relations between entities as well as the metadata attached to
+    each entity and relation. The current distribution of JUNG
+    includes implementations of a number of algorithms from graph
+    theory, data mining, and social network analysis, such as
+    routines for clustering, decomposition, optimization, random
+    graph generation, statistical analysis, and calculation of
+    network distances, flows, and importance measures (centrality,
+    PageRank, HITS, etc.). JUNG also provides a visualization
+    framework that makes it easy to construct tools for the
+    interactive exploration of network data. Users can use one of
+    the layout algorithms provided, or use the framework to create
+    their own custom layouts. In addition, filtering mechanisms are
+    provided which allow users to focus their attention, or their
+    algorithms, on specific portions of the graph.
+  </description>
+  <modules>
+    <module>jung-api</module>
+    <module>jung-graph-impl</module>
+    <module>jung-algorithms</module>
+    <module>jung-io</module>
+    <module>jung-visualization</module>
+    <module>jung-samples</module>
+  </modules>
+  <prerequisites>
+    <maven>3.1.1</maven>
+  </prerequisites>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    <java.version>1.6</java.version>
+
+    <!-- TODO(cgruber): Audit and update dependencies, particularly google deps. -->
+    <junit.version>4.12</junit.version>
+    <guava.version>19.0</guava.version>
+    
+    <cobertura.plugin.version>2.7</cobertura.plugin.version>
+    <compiler.plugin.version>3.5.1</compiler.plugin.version>
+    <gpg.plugin.version>1.6</gpg.plugin.version>
+    <jar.plugin.version>2.6</jar.plugin.version>
+    <javadoc.plugin.version>2.10.4</javadoc.plugin.version>
+    <jxr.plugin.version>2.5</jxr.plugin.version>
+    <pmd.plugin.version>3.6</pmd.plugin.version>
+    <release.plugin.version>2.5.3</release.plugin.version>
+    <surefire.plugin.version>2.19.1</surefire.plugin.version>
+    <source.plugin.version>3.0.1</source.plugin.version>    
+  </properties>
+  <developers>
+    <developer>
+      <id>eflat</id>
+      <name>Joshua</name>
+      <email>joshua.omadadhain+maven at gmail.com</email>
+      <url>https://sites.google.com/site/joshuaomadadhain</url>
+      <organization>Google</organization>
+      <organizationUrl>http://www.google.com</organizationUrl>
+      <timezone>-8</timezone>
+      <roles>
+        <role>Owner</role>
+        <role>Developer</role>
+      </roles>
+    </developer>
+    <developer>
+      <id>offkey</id>
+      <name>Danyel</name>
+      <email>offkey at sf.net</email>
+      <organization>Microsoft Research</organization>
+      <organizationUrl>http://research.microsoft.com</organizationUrl>
+      <timezone>-8</timezone>
+      <roles>
+        <role>Owner</role>
+        <role>Developer</role>
+      </roles>
+    </developer>
+    <developer>
+      <id>tomnelson</id>
+      <name>Tom</name>
+      <email>tomnelson at sf.net</email>
+      <organization>ICCI</organization>
+      <organizationUrl>http://www.intergratedcc.com</organizationUrl>
+      <timezone>-5</timezone>
+      <roles>
+        <role>Developer</role>
+      </roles>
+    </developer>
+  </developers>
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <version>${junit.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.guava</groupId>
+        <artifactId>guava</artifactId>
+        <version>${guava.version}</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>${compiler.plugin.version}</version>
+          <configuration>
+            <source>${java.version}</source>
+            <target>${java.version}</target>
+          </configuration>
+        </plugin>
+        <plugin>
+          <artifactId>maven-jar-plugin</artifactId>
+          <version>${jar.plugin.version}</version>
+          <configuration>
+            <archive>
+              <manifest>
+                <addClasspath>true</addClasspath>
+              </manifest>
+            </archive>
+          </configuration>
+        </plugin>
+        <plugin>
+          <artifactId>maven-release-plugin</artifactId>
+          <version>{release.plugin.version}</version>
+          <configuration>
+            <mavenExecutorId>forked-path</mavenExecutorId>
+            <useReleaseProfile>false</useReleaseProfile>
+            <arguments>${arguments} -Psonatype-oss-release</arguments>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+  <reporting>
+    <plugins>
+      <plugin>
+        <!-- generates aggregated javadoc for root project -->
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>${javadoc.plugin.version}</version>
+        <configuration>
+          <aggregate>true</aggregate>
+          <links>
+            <link>http://docs.oracle.com/javaee/6/api/</link>
+            <link>http://docs.oracle.com/javase/6/docs/api/</link>
+          </links>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-report-plugin</artifactId>
+        <version>${surefire.plugin.version}</version>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>cobertura-maven-plugin</artifactId>
+        <version>${cobertura.plugin.version}</version>
+      </plugin>
+      <plugin>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <version>${pmd.plugin.version}</version>
+        <configuration>
+          <targetJdk>${java.version}</targetJdk>
+          <linkXref>true</linkXref>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-jxr-plugin</artifactId>
+        <version>${jxr.plugin.version}</version>
+      </plugin>
+    </plugins>
+  </reporting>
+  <distributionManagement>
+    <snapshotRepository>
+      <id>sonatype-nexus-snapshots</id>
+      <name>Sonatype Nexus Snapshots</name>
+      <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
+    </snapshotRepository>
+    <repository>
+      <id>sonatype-nexus-staging</id>
+      <name>Nexus Release Repository</name>
+      <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+    </repository>
+  </distributionManagement>
+  <profiles>
+    <profile>
+      <id>sonatype-oss-release</id>
+      <build>
+        <plugins>
+          <plugin>
+            <artifactId>maven-source-plugin</artifactId>
+            <version>${source.plugin.version}</version>
+            <executions>
+              <execution>
+                <id>attach-sources</id>
+                <goals>
+                  <goal>jar-no-fork</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+          <plugin>
+            <artifactId>maven-javadoc-plugin</artifactId>
+            <version>${javadoc.plugin.version}</version>
+            <executions>
+              <execution>
+                <id>attach-javadocs</id>
+                <goals>
+                  <goal>jar</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+          <plugin>
+            <artifactId>maven-gpg-plugin</artifactId>
+            <version>${gpg.plugin.version}</version>
+            <executions>
+              <execution>
+                <id>sign-artifacts</id>
+                <phase>verify</phase>
+                <goals>
+                  <goal>sign</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>
diff --git a/tools/deploy_snapshots.sh b/tools/deploy_snapshots.sh
new file mode 100755
index 0000000..1544f4a
--- /dev/null
+++ b/tools/deploy_snapshots.sh
@@ -0,0 +1,13 @@
+# see https://coderwall.com/p/9b_lfq
+
+if [ "$TRAVIS_REPO_SLUG" == "jrtom/jung" ] && \
+   [ "$TRAVIS_JDK_VERSION" == "oraclejdk7" ] && \
+   [ "$TRAVIS_PULL_REQUEST" == "false" ] && \
+   [ "$TRAVIS_BRANCH" == "master" ]; then
+  echo -e "Publishing maven snapshot...\n"
+
+  mvn clean source:jar deploy --settings="tools/settings.xml" -DskipTests=true -Dmaven.javadoc.skip=true
+
+  echo -e "Published maven snapshot"
+fi
+
diff --git a/tools/mvn-deploy.sh b/tools/mvn-deploy.sh
new file mode 100755
index 0000000..ec4b7a0
--- /dev/null
+++ b/tools/mvn-deploy.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+if [ $# -lt 1 ]; then
+  echo "usage $0 <ssl-key> [<param> ...]"
+  exit 1;
+fi
+key=$1
+shift
+
+#validate key
+keystatus=$(gpg --list-keys | grep ${key} | awk '{print $1}')
+if [ "${keystatus}" != "pub" ]; then
+  echo "Could not find public key with label ${key}"
+  echo -n "Available keys from: "
+  gpg --list-keys | grep --invert-match '^sub'
+
+  exit 64
+fi
+
+mvn "$@" -P '!examples' -P sonatype-oss-release \
+    -Dgpg.skip=false -Dgpg.keyname=${key} \
+    clean site:jar deploy
diff --git a/tools/settings.xml b/tools/settings.xml
new file mode 100644
index 0000000..e42ffd3
--- /dev/null
+++ b/tools/settings.xml
@@ -0,0 +1,9 @@
+<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
+  <servers>
+    <server>
+      <id>sonatype-nexus-snapshots</id>
+      <username>${env.CI_DEPLOY_USERNAME}</username>
+      <password>${env.CI_DEPLOY_PASSWORD}</password>
+    </server>
+  </servers>
+</settings>

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/libjung-java.git



More information about the debian-med-commit mailing list