View Javadoc
1   /*
2    * Copyright (c) 2003, The JUNG Authors
3    * All rights reserved.
4    * 
5    * This software is open-source under the BSD license; see either "license.txt"
6    * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
7    * 
8    */
9   package edu.uci.ics.jung.samples;
10  
11  import java.awt.BorderLayout;
12  import java.awt.Color;
13  import java.awt.Component;
14  import java.awt.Container;
15  import java.awt.Dimension;
16  import java.awt.GridLayout;
17  import java.awt.Shape;
18  import java.awt.event.ActionEvent;
19  import java.awt.event.ActionListener;
20  import java.awt.geom.Point2D;
21  import java.lang.reflect.Constructor;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Set;
27  
28  import javax.swing.BorderFactory;
29  import javax.swing.DefaultListCellRenderer;
30  import javax.swing.JApplet;
31  import javax.swing.JButton;
32  import javax.swing.JComboBox;
33  import javax.swing.JComponent;
34  import javax.swing.JFrame;
35  import javax.swing.JList;
36  import javax.swing.JOptionPane;
37  import javax.swing.JPanel;
38  
39  import com.google.common.base.Function;
40  import com.google.common.base.Predicate;
41  
42  import edu.uci.ics.jung.algorithms.layout.CircleLayout;
43  import edu.uci.ics.jung.algorithms.layout.FRLayout;
44  import edu.uci.ics.jung.algorithms.layout.ISOMLayout;
45  import edu.uci.ics.jung.algorithms.layout.KKLayout;
46  import edu.uci.ics.jung.algorithms.layout.Layout;
47  import edu.uci.ics.jung.algorithms.layout.SpringLayout;
48  import edu.uci.ics.jung.algorithms.layout.SpringLayout2;
49  import edu.uci.ics.jung.graph.Graph;
50  import edu.uci.ics.jung.graph.util.Pair;
51  import edu.uci.ics.jung.graph.util.TestGraphs;
52  import edu.uci.ics.jung.visualization.DefaultVisualizationModel;
53  import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
54  import edu.uci.ics.jung.visualization.VisualizationModel;
55  import edu.uci.ics.jung.visualization.VisualizationViewer;
56  import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
57  import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
58  import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
59  import edu.uci.ics.jung.visualization.control.ScalingControl;
60  import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer;
61  import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
62  import edu.uci.ics.jung.visualization.layout.LayoutTransition;
63  import edu.uci.ics.jung.visualization.subLayout.GraphCollapser;
64  import edu.uci.ics.jung.visualization.util.Animator;
65  import edu.uci.ics.jung.visualization.util.PredicatedParallelEdgeIndexFunction;
66  
67  
68  /**
69   * A demo that shows how collections of vertices can be collapsed
70   * into a single vertex. In this demo, the vertices that are
71   * collapsed are those mouse-picked by the user. Any criteria
72   * could be used to form the vertex collections to be collapsed,
73   * perhaps some common characteristic of those vertex objects.
74   * 
75   * Note that the collection types don't use generics in this
76   * demo, because the vertices are of two types: String for plain
77   * vertices, and {@code Graph<String, Number>} for the collapsed vertices.
78   * 
79   * @author Tom Nelson
80   * 
81   */
82  @SuppressWarnings("serial")
83  public class VertexCollapseDemoWithLayouts extends JApplet {
84  
85      String instructions =
86          "<html>Use the mouse to select multiple vertices"+
87          "<p>either by dragging a region, or by shift-clicking"+
88          "<p>on multiple vertices."+
89          "<p>After you select vertices, use the Collapse button"+
90          "<p>to combine them into a single vertex."+
91          "<p>Select a 'collapsed' vertex and use the Expand button"+
92          "<p>to restore the collapsed vertices."+
93          "<p>The Restore button will restore the original graph."+
94          "<p>If you select 2 (and only 2) vertices, then press"+
95          "<p>the Compress Edges button, parallel edges between"+
96          "<p>those two vertices will no longer be expanded."+
97          "<p>If you select 2 (and only 2) vertices, then press"+
98          "<p>the Expand Edges button, parallel edges between"+
99          "<p>those two vertices will be expanded."+
100         "<p>You can drag the vertices with the mouse." +
101         "<p>Use the 'Picking'/'Transforming' combo-box to switch"+
102         "<p>between picking and transforming mode.</html>";
103     /**
104      * the graph
105      */
106     @SuppressWarnings("rawtypes")
107 	Graph graph;
108     @SuppressWarnings("rawtypes")
109 	Graph collapsedGraph;
110 
111     /**
112      * the visual component and renderer for the graph
113      */
114     @SuppressWarnings("rawtypes")
115 	VisualizationViewer vv;
116     
117     @SuppressWarnings("rawtypes")
118 	Layout layout;
119     
120     GraphCollapser collapser;
121 
122     @SuppressWarnings({ "unchecked", "rawtypes" })
123 	public VertexCollapseDemoWithLayouts() {
124         
125         // create a simple graph for the demo
126         graph = 
127             TestGraphs.getOneComponentGraph();
128         collapsedGraph = graph;
129         collapser = new GraphCollapser(graph);
130         
131         layout = new FRLayout(graph);
132 
133         Dimension preferredSize = new Dimension(400,400);
134         final VisualizationModel visualizationModel = 
135             new DefaultVisualizationModel(layout, preferredSize);
136         vv =  new VisualizationViewer(visualizationModel, preferredSize);
137         
138         vv.getRenderContext().setVertexShapeTransformer(new ClusterVertexShapeFunction());
139         
140         final PredicatedParallelEdgeIndexFunction eif = PredicatedParallelEdgeIndexFunction.getInstance();
141         final Set exclusions = new HashSet();
142         eif.setPredicate(new Predicate() {
143 
144 			public boolean apply(Object e) {
145 				
146 				return exclusions.contains(e);
147 			}});
148         
149         
150         vv.getRenderContext().setParallelEdgeIndexFunction(eif);
151 
152         vv.setBackground(Color.white);
153         
154         // add a listener for ToolTips
155         vv.setVertexToolTipTransformer(new ToStringLabeller() {
156 
157 			/* (non-Javadoc)
158 			 * @see edu.uci.ics.jung.visualization.decorators.DefaultToolTipFunction#getToolTipText(java.lang.Object)
159 			 */
160 			@Override
161 			public String apply(Object v) {
162 				if(v instanceof Graph) {
163 					return ((Graph)v).getVertices().toString();
164 				}
165 				return super.apply(v);
166 			}});
167         
168         /**
169          * the regular graph mouse for the normal view
170          */
171         final DefaultModalGraphMouse graphMouse = new DefaultModalGraphMouse();
172 
173         vv.setGraphMouse(graphMouse);
174         
175         Container content = getContentPane();
176         GraphZoomScrollPane gzsp = new GraphZoomScrollPane(vv);
177         content.add(gzsp);
178         
179         JComboBox modeBox = graphMouse.getModeComboBox();
180         modeBox.addItemListener(graphMouse.getModeListener());
181         graphMouse.setMode(ModalGraphMouse.Mode.PICKING);
182         
183         final ScalingControl scaler = new CrossoverScalingControl();
184 
185         JButton plus = new JButton("+");
186         plus.addActionListener(new ActionListener() {
187             public void actionPerformed(ActionEvent e) {
188                 scaler.scale(vv, 1.1f, vv.getCenter());
189             }
190         });
191         JButton minus = new JButton("-");
192         minus.addActionListener(new ActionListener() {
193             public void actionPerformed(ActionEvent e) {
194                 scaler.scale(vv, 1/1.1f, vv.getCenter());
195             }
196         });
197         
198         JButton collapse = new JButton("Collapse");
199         collapse.addActionListener(new ActionListener() {
200 
201             public void actionPerformed(ActionEvent e) {
202                 Collection picked = new HashSet(vv.getPickedVertexState().getPicked());
203                 if(picked.size() > 1) {
204                     Graph inGraph = layout.getGraph();
205                     Graph clusterGraph = collapser.getClusterGraph(inGraph, picked);
206 
207                     Graph g = collapser.collapse(layout.getGraph(), clusterGraph);
208                     collapsedGraph = g;
209                     double sumx = 0;
210                     double sumy = 0;
211                     for(Object v : picked) {
212                     	Point2D p = (Point2D)layout.apply(v);
213                     	sumx += p.getX();
214                     	sumy += p.getY();
215                     }
216                     Point2D cp = new Point2D.Double(sumx/picked.size(), sumy/picked.size());
217                     vv.getRenderContext().getParallelEdgeIndexFunction().reset();
218                     layout.setGraph(g);
219                     layout.setLocation(clusterGraph, cp);
220                     vv.getPickedVertexState().clear();
221                     vv.repaint();
222                 }
223             }});
224         
225         JButton compressEdges = new JButton("Compress Edges");
226         compressEdges.addActionListener(new ActionListener() {
227 
228 			public void actionPerformed(ActionEvent e) {
229 				Collection picked = vv.getPickedVertexState().getPicked();
230 				if(picked.size() == 2) {
231 					Pair pair = new Pair(picked);
232 					Graph graph = layout.getGraph();
233 					Collection edges = new HashSet(graph.getIncidentEdges(pair.getFirst()));
234 					edges.retainAll(graph.getIncidentEdges(pair.getSecond()));
235 					exclusions.addAll(edges);
236 					vv.repaint();
237 				}
238 				
239 			}});
240         
241         JButton expandEdges = new JButton("Expand Edges");
242         expandEdges.addActionListener(new ActionListener() {
243 
244 			public void actionPerformed(ActionEvent e) {
245 				Collection picked = vv.getPickedVertexState().getPicked();
246 				if(picked.size() == 2) {
247 					Pair pair = new Pair(picked);
248 					Graph graph = layout.getGraph();
249 					Collection edges = new HashSet(graph.getIncidentEdges(pair.getFirst()));
250 					edges.retainAll(graph.getIncidentEdges(pair.getSecond()));
251 					exclusions.removeAll(edges);
252 					vv.repaint();
253 				}
254 				
255 			}});
256         
257         JButton expand = new JButton("Expand");
258         expand.addActionListener(new ActionListener() {
259 
260             public void actionPerformed(ActionEvent e) {
261                 Collection picked = new HashSet(vv.getPickedVertexState().getPicked());
262                 for(Object v : picked) {
263                     if(v instanceof Graph) {
264                         
265                         Graph g = collapser.expand(layout.getGraph(), (Graph)v);
266                         vv.getRenderContext().getParallelEdgeIndexFunction().reset();
267                         layout.setGraph(g);
268                     }
269                     vv.getPickedVertexState().clear();
270                    vv.repaint();
271                 }
272             }});
273         
274         JButton reset = new JButton("Reset");
275         reset.addActionListener(new ActionListener() {
276 
277             public void actionPerformed(ActionEvent e) {
278                 layout.setGraph(graph);
279                 exclusions.clear();
280                 vv.repaint();
281             }});
282         
283         JButton help = new JButton("Help");
284         help.addActionListener(new ActionListener() {
285             public void actionPerformed(ActionEvent e) {
286                 JOptionPane.showMessageDialog((JComponent)e.getSource(), instructions, "Help", JOptionPane.PLAIN_MESSAGE);
287             }
288         });
289         Class[] combos = getCombos();
290         final JComboBox jcb = new JComboBox(combos);
291         // use a renderer to shorten the layout name presentation
292         jcb.setRenderer(new DefaultListCellRenderer() {
293             public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
294                 String valueString = value.toString();
295                 valueString = valueString.substring(valueString.lastIndexOf('.')+1);
296                 return super.getListCellRendererComponent(list, valueString, index, isSelected,
297                         cellHasFocus);
298             }
299         });
300         jcb.addActionListener(new LayoutChooser(jcb, vv));
301         jcb.setSelectedItem(FRLayout.class);
302 
303 
304         JPanel controls = new JPanel();
305         JPanel zoomControls = new JPanel(new GridLayout(2,1));
306         zoomControls.setBorder(BorderFactory.createTitledBorder("Zoom"));
307         zoomControls.add(plus);
308         zoomControls.add(minus);
309         controls.add(zoomControls);
310         JPanel collapseControls = new JPanel(new GridLayout(3,1));
311         collapseControls.setBorder(BorderFactory.createTitledBorder("Picked"));
312         collapseControls.add(collapse);
313         collapseControls.add(expand);
314         collapseControls.add(compressEdges);
315         collapseControls.add(expandEdges);
316         collapseControls.add(reset);
317         controls.add(collapseControls);
318         controls.add(modeBox);
319         controls.add(help);
320         controls.add(jcb);
321         content.add(controls, BorderLayout.SOUTH);
322     }
323     
324     /**
325      * a demo class that will create a vertex shape that is either a
326      * polygon or star. The number of sides corresponds to the number
327      * of vertices that were collapsed into the vertex represented by
328      * this shape.
329      * 
330      * @author Tom Nelson
331      *
332      * @param <V> the vertex type
333      */
334     class ClusterVertexShapeFunction<V> extends EllipseVertexShapeTransformer<V> {
335 
336         ClusterVertexShapeFunction() {
337             setSizeTransformer(new ClusterVertexSizeFunction<V>(20));
338         }
339         @Override
340         public Shape apply(V v) {
341             if(v instanceof Graph) {
342                 @SuppressWarnings("rawtypes")
343 				int size = ((Graph)v).getVertexCount();
344                 if (size < 8) {   
345                     int sides = Math.max(size, 3);
346                     return factory.getRegularPolygon(v, sides);
347                 }
348                 else {
349                     return factory.getRegularStar(v, size);
350                 }
351             }
352             return super.apply(v);
353         }
354     }
355     
356     /**
357      * A demo class that will make vertices larger if they represent
358      * a collapsed collection of original vertices
359      * @author Tom Nelson
360      *
361      * @param <V> the vertex type
362      */
363     class ClusterVertexSizeFunction<V> implements Function<V,Integer> {
364     	int size;
365         public ClusterVertexSizeFunction(Integer size) {
366             this.size = size;
367         }
368 
369         public Integer apply(V v) {
370             if(v instanceof Graph) {
371                 return 30;
372             }
373             return size;
374         }
375     }
376 
377 	private class LayoutChooser implements ActionListener
378     {
379         private final JComboBox<?> jcb;
380         @SuppressWarnings("rawtypes")
381 		private final VisualizationViewer vv;
382 
383         private LayoutChooser(JComboBox<?> jcb, VisualizationViewer<Object, ?> vv)
384         {
385             super();
386             this.jcb = jcb;
387             this.vv = vv;
388         }
389 
390         @SuppressWarnings({ "unchecked", "rawtypes" })
391         public void actionPerformed(ActionEvent arg0)
392         {
393             Object[] constructorArgs =
394                 { collapsedGraph };
395 
396 			Class<? extends Layout> layoutC = 
397                 (Class<? extends Layout>) jcb.getSelectedItem();
398             try
399             {
400                 Constructor<? extends Layout> constructor = layoutC
401                         .getConstructor(new Class[] {Graph.class});
402                 Object o = constructor.newInstance(constructorArgs);
403                 Layout l = (Layout) o;
404                 l.setInitializer(vv.getGraphLayout());
405                 l.setSize(vv.getSize());
406                 layout = l;
407 				LayoutTransition lt =
408 					new LayoutTransition(vv, vv.getGraphLayout(), l);
409 				Animator animator = new Animator(lt);
410 				animator.start();
411 				vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
412 				vv.repaint();
413                 
414             }
415             catch (Exception e)
416             {
417                 e.printStackTrace();
418             }
419         }
420     }
421     /**
422      * @return
423      */
424     @SuppressWarnings({ "unchecked", "rawtypes" })
425     private Class<? extends Layout>[] getCombos()
426     {
427         List<Class<? extends Layout>> layouts = new ArrayList<Class<? extends Layout>>();
428         layouts.add(KKLayout.class);
429         layouts.add(FRLayout.class);
430         layouts.add(CircleLayout.class);
431         layouts.add(SpringLayout.class);
432         layouts.add(SpringLayout2.class);
433         layouts.add(ISOMLayout.class);
434         return layouts.toArray(new Class[0]);
435     }
436 
437     public static void main(String[] args) {
438         JFrame f = new JFrame();
439         f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
440         f.getContentPane().add(new VertexCollapseDemoWithLayouts());
441         f.pack();
442         f.setVisible(true);
443     }
444 }
445 
446