View Javadoc
1   /*
2   * Copyright (c) 2003, The JUNG Authors
3   *
4   * All rights reserved.
5   *
6   * This software is open-source under the BSD license; see either
7   * "license.txt" or
8   * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
9   */
10  package edu.uci.ics.jung.samples;
11  
12  import java.awt.BasicStroke;
13  import java.awt.BorderLayout;
14  import java.awt.Color;
15  import java.awt.Container;
16  import java.awt.Dimension;
17  import java.awt.GridLayout;
18  import java.awt.Paint;
19  import java.awt.Stroke;
20  import java.awt.event.ActionEvent;
21  import java.awt.event.ActionListener;
22  import java.awt.event.ItemEvent;
23  import java.awt.event.ItemListener;
24  import java.awt.geom.Point2D;
25  import java.io.BufferedReader;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.InputStreamReader;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Set;
32  
33  import javax.swing.BorderFactory;
34  import javax.swing.Box;
35  import javax.swing.BoxLayout;
36  import javax.swing.JApplet;
37  import javax.swing.JButton;
38  import javax.swing.JFrame;
39  import javax.swing.JPanel;
40  import javax.swing.JSlider;
41  import javax.swing.JToggleButton;
42  import javax.swing.border.TitledBorder;
43  import javax.swing.event.ChangeEvent;
44  import javax.swing.event.ChangeListener;
45  
46  import com.google.common.base.Function;
47  import com.google.common.base.Functions;
48  import com.google.common.base.Supplier;
49  import com.google.common.cache.CacheBuilder;
50  import com.google.common.cache.CacheLoader;
51  import com.google.common.cache.LoadingCache;
52  
53  import edu.uci.ics.jung.algorithms.cluster.EdgeBetweennessClusterer;
54  import edu.uci.ics.jung.algorithms.layout.AggregateLayout;
55  import edu.uci.ics.jung.algorithms.layout.CircleLayout;
56  import edu.uci.ics.jung.algorithms.layout.FRLayout;
57  import edu.uci.ics.jung.algorithms.layout.Layout;
58  import edu.uci.ics.jung.algorithms.layout.util.Relaxer;
59  import edu.uci.ics.jung.graph.Graph;
60  import edu.uci.ics.jung.graph.SparseMultigraph;
61  import edu.uci.ics.jung.io.PajekNetReader;
62  import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
63  import edu.uci.ics.jung.visualization.VisualizationViewer;
64  import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
65  
66  
67  /**
68   * This simple app demonstrates how one can use our algorithms and visualization libraries in unison.
69   * In this case, we generate use the Zachary karate club data set, widely known in the social networks literature, then
70   * we cluster the vertices using an edge-betweenness clusterer, and finally we visualize the graph using
71   * Fruchtermain-Rheingold layout and provide a slider so that the user can adjust the clustering granularity.
72   * @author Scott White
73   */
74  @SuppressWarnings("serial")
75  public class ClusteringDemo extends JApplet {
76  
77  	VisualizationViewer<Number,Number> vv;
78  
79  	LoadingCache<Number, Paint> vertexPaints =
80  			CacheBuilder.newBuilder().build(
81  					CacheLoader.from(Functions.<Paint>constant(Color.white))); 
82  	LoadingCache<Number, Paint> edgePaints =
83  			CacheBuilder.newBuilder().build(
84  					CacheLoader.from(Functions.<Paint>constant(Color.blue))); 
85  
86  	public final Color[] similarColors =
87  	{
88  		new Color(216, 134, 134),
89  		new Color(135, 137, 211),
90  		new Color(134, 206, 189),
91  		new Color(206, 176, 134),
92  		new Color(194, 204, 134),
93  		new Color(145, 214, 134),
94  		new Color(133, 178, 209),
95  		new Color(103, 148, 255),
96  		new Color(60, 220, 220),
97  		new Color(30, 250, 100)
98  	};
99  	
100 	public static void main(String[] args) throws IOException {
101 		
102 		ClusteringDemo cd = new ClusteringDemo();
103 		cd.start();
104 		// Add a restart button so the graph can be redrawn to fit the size of the frame
105 		JFrame jf = new JFrame();
106 		jf.getContentPane().add(cd);
107 		
108 		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
109 		jf.pack();
110 		jf.setVisible(true);
111 	}
112 
113 	public void start() {
114 		InputStream is = this.getClass().getClassLoader().getResourceAsStream("datasets/zachary.net");
115 		BufferedReader br = new BufferedReader( new InputStreamReader( is ));
116         
117         try
118         {
119             setUpView(br);
120         }
121         catch (IOException e)
122         {
123             System.out.println("Error in loading graph");
124             e.printStackTrace();
125         }
126 	}
127 
128 	private void setUpView(BufferedReader br) throws IOException {
129 		
130 		Supplier<Number> vertexFactory = new Supplier<Number>() {
131             int n = 0;
132             public Number get() { return n++; }
133         };
134         Supplier<Number> edgeFactory = new Supplier<Number>()  {
135             int n = 0;
136             public Number get() { return n++; }
137         };
138 
139         PajekNetReader<Graph<Number, Number>, Number,Number> pnr = 
140             new PajekNetReader<Graph<Number, Number>, Number,Number>(vertexFactory, edgeFactory);
141         
142         final Graph<Number,Number> graph = new SparseMultigraph<Number, Number>();
143         
144         pnr.load(br, graph);
145 
146 		//Create a simple layout frame
147         //specify the Fruchterman-Rheingold layout algorithm
148         final AggregateLayout<Number,Number> layout = 
149         	new AggregateLayout<Number,Number>(new FRLayout<Number,Number>(graph));
150 
151 		vv = new VisualizationViewer<Number,Number>(layout);
152 		vv.setBackground( Color.white );
153 		//Tell the renderer to use our own customized color rendering
154 		vv.getRenderContext().setVertexFillPaintTransformer(vertexPaints);
155 		vv.getRenderContext().setVertexDrawPaintTransformer(new Function<Number,Paint>() {
156 			public Paint apply(Number v) {
157 				if(vv.getPickedVertexState().isPicked(v)) {
158 					return Color.cyan;
159 				} else {
160 					return Color.BLACK;
161 				}
162 			}
163 		});
164 
165 		vv.getRenderContext().setEdgeDrawPaintTransformer(edgePaints);
166 
167 		vv.getRenderContext().setEdgeStrokeTransformer(new Function<Number,Stroke>() {
168                 protected final Stroke THIN = new BasicStroke(1);
169                 protected final Stroke THICK= new BasicStroke(2);
170                 public Stroke apply(Number e)
171                 {
172                     Paint c = edgePaints.getUnchecked(e);
173                     if (c == Color.LIGHT_GRAY)
174                         return THIN;
175                     else 
176                         return THICK;
177                 }
178             });
179 
180 		//add restart button
181 		JButton scramble = new JButton("Restart");
182 		scramble.addActionListener(new ActionListener() {
183 			public void actionPerformed(ActionEvent arg0) {
184 				Layout<Number, Number> layout = vv.getGraphLayout();
185 				layout.initialize();
186 				Relaxer relaxer = vv.getModel().getRelaxer();
187 				if(relaxer != null) {
188 					relaxer.stop();
189 					relaxer.prerelax();
190 					relaxer.relax();
191 				}
192 			}
193 
194 		});
195 		
196 		DefaultModalGraphMouse<Number, Number> gm = new DefaultModalGraphMouse<Number, Number>();
197 		vv.setGraphMouse(gm);
198 		
199 		final JToggleButton groupVertices = new JToggleButton("Group Clusters");
200 
201 		//Create slider to adjust the number of edges to remove when clustering
202 		final JSlider edgeBetweennessSlider = new JSlider(JSlider.HORIZONTAL);
203         edgeBetweennessSlider.setBackground(Color.WHITE);
204 		edgeBetweennessSlider.setPreferredSize(new Dimension(210, 50));
205 		edgeBetweennessSlider.setPaintTicks(true);
206 		edgeBetweennessSlider.setMaximum(graph.getEdgeCount());
207 		edgeBetweennessSlider.setMinimum(0);
208 		edgeBetweennessSlider.setValue(0);
209 		edgeBetweennessSlider.setMajorTickSpacing(10);
210 		edgeBetweennessSlider.setPaintLabels(true);
211 		edgeBetweennessSlider.setPaintTicks(true);
212 
213 //		edgeBetweennessSlider.setBorder(BorderFactory.createLineBorder(Color.black));
214 		//TO DO: edgeBetweennessSlider.add(new JLabel("Node Size (PageRank With Priors):"));
215 		//I also want the slider value to appear
216 		final JPanel eastControls = new JPanel();
217 		eastControls.setOpaque(true);
218 		eastControls.setLayout(new BoxLayout(eastControls, BoxLayout.Y_AXIS));
219 		eastControls.add(Box.createVerticalGlue());
220 		eastControls.add(edgeBetweennessSlider);
221 
222 		final String COMMANDSTRING = "Edges removed for clusters: ";
223 		final String eastSize = COMMANDSTRING + edgeBetweennessSlider.getValue();
224 		
225 		final TitledBorder sliderBorder = BorderFactory.createTitledBorder(eastSize);
226 		eastControls.setBorder(sliderBorder);
227 		//eastControls.add(eastSize);
228 		eastControls.add(Box.createVerticalGlue());
229 		
230 		groupVertices.addItemListener(new ItemListener() {
231 			public void itemStateChanged(ItemEvent e) {
232 					clusterAndRecolor(layout, edgeBetweennessSlider.getValue(), 
233 							similarColors, e.getStateChange() == ItemEvent.SELECTED);
234 					vv.repaint();
235 			}});
236 
237 
238 		clusterAndRecolor(layout, 0, similarColors, groupVertices.isSelected());
239 
240 		edgeBetweennessSlider.addChangeListener(new ChangeListener() {
241 			public void stateChanged(ChangeEvent e) {
242 				JSlider source = (JSlider) e.getSource();
243 				if (!source.getValueIsAdjusting()) {
244 					int numEdgesToRemove = source.getValue();
245 					clusterAndRecolor(layout, numEdgesToRemove, similarColors,
246 							groupVertices.isSelected());
247 					sliderBorder.setTitle(
248 						COMMANDSTRING + edgeBetweennessSlider.getValue());
249 					eastControls.repaint();
250 					vv.validate();
251 					vv.repaint();
252 				}
253 			}
254 		});
255 
256 		Container content = getContentPane();
257 		content.add(new GraphZoomScrollPane(vv));
258 		JPanel south = new JPanel();
259 		JPanel grid = new JPanel(new GridLayout(2,1));
260 		grid.add(scramble);
261 		grid.add(groupVertices);
262 		south.add(grid);
263 		south.add(eastControls);
264 		JPanel p = new JPanel();
265 		p.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
266 		p.add(gm.getModeComboBox());
267 		south.add(p);
268 		content.add(south, BorderLayout.SOUTH);
269 	}
270 
271 	public void clusterAndRecolor(AggregateLayout<Number,Number> layout,
272 		int numEdgesToRemove,
273 		Color[] colors, boolean groupClusters) {
274 		//Now cluster the vertices by removing the top 50 edges with highest betweenness
275 		//		if (numEdgesToRemove == 0) {
276 		//			colorCluster( g.getVertices(), colors[0] );
277 		//		} else {
278 		
279 		Graph<Number,Number> g = layout.getGraph();
280         layout.removeAll();
281 
282 		EdgeBetweennessClusterer<Number,Number> clusterer =
283 			new EdgeBetweennessClusterer<Number,Number>(numEdgesToRemove);
284 		Set<Set<Number>> clusterSet = clusterer.apply(g);
285 		List<Number> edges = clusterer.getEdgesRemoved();
286 
287 		int i = 0;
288 		//Set the colors of each node so that each cluster's vertices have the same color
289 		for (Iterator<Set<Number>> cIt = clusterSet.iterator(); cIt.hasNext();) {
290 
291 			Set<Number> vertices = cIt.next();
292 			Color c = colors[i % colors.length];
293 
294 			colorCluster(vertices, c);
295 			if(groupClusters == true) {
296 				groupCluster(layout, vertices);
297 			}
298 			i++;
299 		}
300 		for (Number e : g.getEdges()) {
301 
302 			if (edges.contains(e)) {
303 				edgePaints.put(e, Color.lightGray);
304 			} else {
305 				edgePaints.put(e, Color.black);
306 			}
307 		}
308 
309 	}
310 
311 	private void colorCluster(Set<Number> vertices, Color c) {
312 		for (Number v : vertices) {
313 			vertexPaints.put(v, c);
314 		}
315 	}
316 	
317 	private void groupCluster(AggregateLayout<Number,Number> layout, Set<Number> vertices) {
318 		if(vertices.size() < layout.getGraph().getVertexCount()) {
319 			Point2D center = layout.apply(vertices.iterator().next());
320 			Graph<Number,Number> subGraph = SparseMultigraph.<Number,Number>getFactory().get();
321 			for(Number v : vertices) {
322 				subGraph.addVertex(v);
323 			}
324 			Layout<Number,Number> subLayout = 
325 				new CircleLayout<Number,Number>(subGraph);
326 			subLayout.setInitializer(vv.getGraphLayout());
327 			subLayout.setSize(new Dimension(40,40));
328 
329 			layout.put(subLayout,center);
330 			vv.repaint();
331 		}
332 	}
333 }