View Javadoc

1   /*
2    * Copyright (c) 2003, the JUNG Project and the Regents of the University of
3    * California All rights reserved.
4    * 
5    * This software is open-source under the BSD license; see either "license.txt"
6    * or http://jung.sourceforge.net/license.txt 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.Container;
14  import java.awt.Dimension;
15  import java.awt.Font;
16  import java.awt.FontMetrics;
17  import java.awt.Graphics;
18  import java.awt.GridLayout;
19  import java.awt.Image;
20  import java.awt.Paint;
21  import java.awt.Shape;
22  import java.awt.event.ActionEvent;
23  import java.awt.event.ActionListener;
24  import java.awt.event.ItemEvent;
25  import java.awt.event.ItemListener;
26  import java.awt.geom.AffineTransform;
27  import java.awt.geom.Point2D;
28  import java.awt.geom.Rectangle2D;
29  import java.util.HashMap;
30  import java.util.Map;
31  
32  import javax.swing.BorderFactory;
33  import javax.swing.Icon;
34  import javax.swing.ImageIcon;
35  import javax.swing.JApplet;
36  import javax.swing.JButton;
37  import javax.swing.JCheckBox;
38  import javax.swing.JComboBox;
39  import javax.swing.JFrame;
40  import javax.swing.JPanel;
41  
42  import org.apache.commons.collections15.Transformer;
43  
44  import edu.uci.ics.jung.algorithms.layout.FRLayout;
45  import edu.uci.ics.jung.algorithms.layout.Layout;
46  import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer;
47  import edu.uci.ics.jung.graph.DirectedSparseGraph;
48  import edu.uci.ics.jung.graph.util.EdgeType;
49  import edu.uci.ics.jung.visualization.FourPassImageShaper;
50  import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
51  import edu.uci.ics.jung.visualization.Layer;
52  import edu.uci.ics.jung.visualization.LayeredIcon;
53  import edu.uci.ics.jung.visualization.RenderContext;
54  import edu.uci.ics.jung.visualization.VisualizationViewer;
55  import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
56  import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
57  import edu.uci.ics.jung.visualization.control.ScalingControl;
58  import edu.uci.ics.jung.visualization.decorators.DefaultVertexIconTransformer;
59  import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer;
60  import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
61  import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
62  import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
63  import edu.uci.ics.jung.visualization.decorators.VertexIconShapeTransformer;
64  import edu.uci.ics.jung.visualization.picking.PickedState;
65  import edu.uci.ics.jung.visualization.renderers.BasicVertexRenderer;
66  import edu.uci.ics.jung.visualization.renderers.Checkmark;
67  import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer;
68  import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer;
69  import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
70  
71  /**
72   * Demonstrates the use of images to represent graph vertices.
73   * The images are supplied via the VertexShapeFunction so that
74   * both the image and its shape can be utilized.
75   * 
76   * The images used in this demo (courtesy of slashdot.org) are
77   * rectangular but with a transparent background. When vertices
78   * are represented by these images, it looks better if the actual
79   * shape of the opaque part of the image is computed so that the
80   * edge arrowheads follow the visual shape of the image. This demo
81   * uses the FourPassImageShaper class to compute the Shape from
82   * an image with transparent background.
83   * 
84   * @author Tom Nelson
85   * 
86   */
87  public class VertexImageShaperDemo extends JApplet {
88  
89      /**
90  	 * 
91  	 */
92  	private static final long serialVersionUID = -4332663871914930864L;
93  	
94  	private static final int VERTEX_COUNT=11;
95  
96  	/**
97       * the graph
98       */
99      DirectedSparseGraph<Number, Number> graph;
100 
101     /**
102      * the visual component and renderer for the graph
103      */
104     VisualizationViewer<Number, Number> vv;
105     
106     /**
107      * some icon names to use
108      */
109     String[] iconNames = {
110             "apple",
111             "os",
112             "x",
113             "linux",
114             "inputdevices",
115             "wireless",
116             "graphics3",
117             "gamespcgames",
118             "humor",
119             "music",
120             "privacy"
121     };
122     
123     public VertexImageShaperDemo() {
124         
125         // create a simple graph for the demo
126         graph = new DirectedSparseGraph<Number,Number>();
127         createGraph(VERTEX_COUNT);
128         
129         // a Map for the labels
130         Map<Number,String> map = new HashMap<Number,String>();
131         for(int i=0; i<VERTEX_COUNT; i++) {
132             map.put(i, iconNames[i%iconNames.length]);
133         }
134         
135         // a Map for the Icons
136         Map<Number,Icon> iconMap = new HashMap<Number,Icon>();
137         for(int i=0; i<VERTEX_COUNT; i++) {
138             String name = "/images/topic"+iconNames[i]+".gif";
139             try {
140                 Icon icon = 
141                     new LayeredIcon(new ImageIcon(VertexImageShaperDemo.class.getResource(name)).getImage());
142                 iconMap.put(i, icon);
143             } catch(Exception ex) {
144                 System.err.println("You need slashdoticons.jar in your classpath to see the image "+name);
145             }
146         }
147         
148         FRLayout<Number, Number> layout = new FRLayout<Number, Number>(graph);
149         layout.setMaxIterations(100);
150         layout.setInitializer(new RandomLocationTransformer<Number>(new Dimension(400,400), 0));
151         vv =  new VisualizationViewer<Number, Number>(layout, new Dimension(400,400));
152         
153         // This demo uses a special renderer to turn outlines on and off.
154         // you do not need to do this in a real application.
155         // Instead, just let vv use the Renderer it already has
156         vv.getRenderer().setVertexRenderer(new DemoRenderer<Number,Number>());
157 
158         Transformer<Number,Paint> vpf = 
159             new PickableVertexPaintTransformer<Number>(vv.getPickedVertexState(), Color.white, Color.yellow);
160         vv.getRenderContext().setVertexFillPaintTransformer(vpf);
161         vv.getRenderContext().setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<Number>(vv.getPickedEdgeState(), Color.black, Color.cyan));
162 
163         vv.setBackground(Color.white);
164         
165         
166         final Transformer<Number,String> vertexStringerImpl = 
167             new VertexStringerImpl<Number,String>(map);
168         vv.getRenderContext().setVertexLabelTransformer(vertexStringerImpl);
169         vv.getRenderContext().setVertexLabelRenderer(new DefaultVertexLabelRenderer(Color.cyan));
170         vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.cyan));
171 //        vv.getRenderContext().setEdgeLabelTransformer(new Transformer<Number,String>() {
172 //        	URL url = getClass().getResource("/images/lightning-s.gif");
173 //			public String transform(Number input) {
174 //				
175 //				return "<html><img src="+url+" height=10 width=21>"+input.toString();
176 //			}});
177         
178         // For this demo only, I use a special class that lets me turn various
179         // features on and off. For a real application, use VertexIconShapeTransformer instead.
180         final DemoVertexIconShapeTransformer<Number> vertexIconShapeTransformer =
181             new DemoVertexIconShapeTransformer<Number>(new EllipseVertexShapeTransformer<Number>());
182         
183         final DemoVertexIconTransformer<Number> vertexIconTransformer =
184         	new DemoVertexIconTransformer<Number>();
185         
186         vertexIconShapeTransformer.setIconMap(iconMap);
187         vertexIconTransformer.setIconMap(iconMap);
188         
189         vv.getRenderContext().setVertexShapeTransformer(vertexIconShapeTransformer);
190         vv.getRenderContext().setVertexIconTransformer(vertexIconTransformer);
191         
192         // un-comment for RStar Tree visual testing
193         //vv.addPostRenderPaintable(new BoundingRectanglePaintable(vv.getRenderContext(), vv.getGraphLayout()));
194 
195         // Get the pickedState and add a listener that will decorate the
196         // Vertex images with a checkmark icon when they are picked
197         PickedState<Number> ps = vv.getPickedVertexState();
198         ps.addItemListener(new PickWithIconListener<Number>(vertexIconTransformer));
199         
200         vv.addPostRenderPaintable(new VisualizationViewer.Paintable(){
201             int x;
202             int y;
203             Font font;
204             FontMetrics metrics;
205             int swidth;
206             int sheight;
207             String str = "Thank You, slashdot.org, for the images!";
208             
209             public void paint(Graphics g) {
210                 Dimension d = vv.getSize();
211                 if(font == null) {
212                     font = new Font(g.getFont().getName(), Font.BOLD, 20);
213                     metrics = g.getFontMetrics(font);
214                     swidth = metrics.stringWidth(str);
215                     sheight = metrics.getMaxAscent()+metrics.getMaxDescent();
216                     x = (d.width-swidth)/2;
217                     y = (int)(d.height-sheight*1.5);
218                 }
219                 g.setFont(font);
220                 Color oldColor = g.getColor();
221                 g.setColor(Color.lightGray);
222                 g.drawString(str, x, y);
223                 g.setColor(oldColor);
224             }
225             public boolean useTransform() {
226                 return false;
227             }
228         });
229 
230         // add a listener for ToolTips
231         vv.setVertexToolTipTransformer(new ToStringLabeller<Number>());
232         
233         Container content = getContentPane();
234         final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
235         content.add(panel);
236         
237         final DefaultModalGraphMouse<Number,Number> graphMouse = new DefaultModalGraphMouse<Number,Number>();
238         vv.setGraphMouse(graphMouse);
239         vv.addKeyListener(graphMouse.getModeKeyListener());
240         final ScalingControl scaler = new CrossoverScalingControl();
241 
242         JButton plus = new JButton("+");
243         plus.addActionListener(new ActionListener() {
244             public void actionPerformed(ActionEvent e) {
245                 scaler.scale(vv, 1.1f, vv.getCenter());
246             }
247         });
248         JButton minus = new JButton("-");
249         minus.addActionListener(new ActionListener() {
250             public void actionPerformed(ActionEvent e) {
251                 scaler.scale(vv, 1/1.1f, vv.getCenter());
252             }
253         });
254 
255         JCheckBox shape = new JCheckBox("Shape");
256         shape.addItemListener(new ItemListener(){
257 
258             public void itemStateChanged(ItemEvent e) {
259                 vertexIconShapeTransformer.setShapeImages(e.getStateChange()==ItemEvent.SELECTED);
260                 vv.repaint();
261             }
262         });
263         shape.setSelected(true);
264 
265         JCheckBox fill = new JCheckBox("Fill");
266         fill.addItemListener(new ItemListener(){
267 
268             public void itemStateChanged(ItemEvent e) {
269                 vertexIconTransformer.setFillImages(e.getStateChange()==ItemEvent.SELECTED);
270                 vv.repaint();
271             }
272         });
273         fill.setSelected(true);
274         
275         JCheckBox drawOutlines = new JCheckBox("Outline");
276         drawOutlines.addItemListener(new ItemListener(){
277 
278             public void itemStateChanged(ItemEvent e) {
279                 vertexIconTransformer.setOutlineImages(e.getStateChange()==ItemEvent.SELECTED);
280                 vv.repaint();
281             }
282         });
283         
284         JComboBox modeBox = graphMouse.getModeComboBox();
285         JPanel modePanel = new JPanel();
286         modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
287         modePanel.add(modeBox);
288         
289         JPanel scaleGrid = new JPanel(new GridLayout(1,0));
290         scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom"));
291         JPanel labelFeatures = new JPanel(new GridLayout(1,0));
292         labelFeatures.setBorder(BorderFactory.createTitledBorder("Image Effects"));
293         JPanel controls = new JPanel();
294         scaleGrid.add(plus);
295         scaleGrid.add(minus);
296         controls.add(scaleGrid);
297         labelFeatures.add(shape);
298         labelFeatures.add(fill);
299         labelFeatures.add(drawOutlines);
300 
301         controls.add(labelFeatures);
302         controls.add(modePanel);
303         content.add(controls, BorderLayout.SOUTH);
304     }
305     
306     /**
307      * When Vertices are picked, add a checkmark icon to the imager.
308      * Remove the icon when a Vertex is unpicked
309      * @author Tom Nelson
310      *
311      */
312     public static class PickWithIconListener<V> implements ItemListener {
313         DefaultVertexIconTransformer<V> imager;
314         Icon checked;
315         
316         public PickWithIconListener(DefaultVertexIconTransformer<V> imager) {
317             this.imager = imager;
318             checked = new Checkmark();
319         }
320 
321         public void itemStateChanged(ItemEvent e) {
322             Icon icon = imager.transform((V)e.getItem());
323             if(icon != null && icon instanceof LayeredIcon) {
324                 if(e.getStateChange() == ItemEvent.SELECTED) {
325                     ((LayeredIcon)icon).add(checked);
326                 } else {
327                     ((LayeredIcon)icon).remove(checked);
328                 }
329             }
330         }
331     }
332     /**
333      * A simple implementation of VertexStringer that
334      * gets Vertex labels from a Map  
335      * 
336      * @author Tom Nelson
337      *
338      *
339      */
340     public static class VertexStringerImpl<V,S> implements Transformer<V,String> {
341         
342         Map<V,String> map = new HashMap<V,String>();
343         
344         boolean enabled = true;
345         
346         public VertexStringerImpl(Map<V,String> map) {
347             this.map = map;
348         }
349         
350         /* (non-Javadoc)
351          * @see edu.uci.ics.jung.graph.decorators.VertexStringer#getLabel(edu.uci.ics.jung.graph.Vertex)
352          */
353         public String transform(V v) {
354             if(isEnabled()) {
355                 return map.get(v);
356             } else {
357                 return "";
358             }
359         }
360         
361         /**
362          * @return Returns the enabled.
363          */
364         public boolean isEnabled() {
365             return enabled;
366         }
367         
368         /**
369          * @param enabled The enabled to set.
370          */
371         public void setEnabled(boolean enabled) {
372             this.enabled = enabled;
373         }
374     }
375     
376     /**
377      * create some vertices
378      * @param count how many to create
379      * @return the Vertices in an array
380      */
381     private void createGraph(int vertexCount) {
382         for (int i = 0; i < vertexCount; i++) {
383             graph.addVertex(i);
384         }
385     	int j=0;
386         graph.addEdge(j++, 0, 1, EdgeType.DIRECTED);
387         graph.addEdge(j++, 3, 0, EdgeType.DIRECTED);
388         graph.addEdge(j++, 0, 4, EdgeType.DIRECTED);
389         graph.addEdge(j++, 4, 5, EdgeType.DIRECTED);
390         graph.addEdge(j++, 5, 3, EdgeType.DIRECTED);
391         graph.addEdge(j++, 2, 1, EdgeType.DIRECTED);
392         graph.addEdge(j++, 4, 1, EdgeType.DIRECTED);
393         graph.addEdge(j++, 8, 2, EdgeType.DIRECTED);
394         graph.addEdge(j++, 3, 8, EdgeType.DIRECTED);
395         graph.addEdge(j++, 6, 7, EdgeType.DIRECTED);
396         graph.addEdge(j++, 7, 5, EdgeType.DIRECTED);
397         graph.addEdge(j++, 0, 9, EdgeType.DIRECTED);
398         graph.addEdge(j++, 9, 8, EdgeType.DIRECTED);
399         graph.addEdge(j++, 7, 6, EdgeType.DIRECTED);
400         graph.addEdge(j++, 6, 5, EdgeType.DIRECTED);
401         graph.addEdge(j++, 4, 2, EdgeType.DIRECTED);
402         graph.addEdge(j++, 5, 4, EdgeType.DIRECTED);
403         graph.addEdge(j++, 4, 10, EdgeType.DIRECTED);
404         graph.addEdge(j++, 10, 4, EdgeType.DIRECTED);
405     }
406 
407     /** 
408      * this class exists only to provide settings to turn on/off shapes and image fill
409      * in this demo.
410      * In a real application, use DefaultVertexIconTransformer instead.
411      * 
412      */
413     public static class DemoVertexIconTransformer<V> extends DefaultVertexIconTransformer<V>
414     	implements Transformer<V,Icon> {
415         
416         boolean fillImages = true;
417         boolean outlineImages = false;
418 
419         /**
420          * @return Returns the fillImages.
421          */
422         public boolean isFillImages() {
423             return fillImages;
424         }
425         /**
426          * @param fillImages The fillImages to set.
427          */
428         public void setFillImages(boolean fillImages) {
429             this.fillImages = fillImages;
430         }
431 
432         public boolean isOutlineImages() {
433             return outlineImages;
434         }
435         public void setOutlineImages(boolean outlineImages) {
436             this.outlineImages = outlineImages;
437         }
438         
439         public Icon transform(V v) {
440             if(fillImages) {
441                 return (Icon)iconMap.get(v);
442             } else {
443                 return null;
444             }
445         }
446     }
447     
448     /** 
449      * this class exists only to provide settings to turn on/off shapes and image fill
450      * in this demo.
451      * In a real application, use VertexIconShapeTransformer instead.
452      * 
453      */
454     public static class DemoVertexIconShapeTransformer<V> extends VertexIconShapeTransformer<V> {
455         
456         boolean shapeImages = true;
457 
458         public DemoVertexIconShapeTransformer(Transformer<V,Shape> delegate) {
459             super(delegate);
460         }
461 
462         /**
463          * @return Returns the shapeImages.
464          */
465         public boolean isShapeImages() {
466             return shapeImages;
467         }
468         /**
469          * @param shapeImages The shapeImages to set.
470          */
471         public void setShapeImages(boolean shapeImages) {
472             shapeMap.clear();
473             this.shapeImages = shapeImages;
474         }
475 
476         public Shape transform(V v) {
477 			Icon icon = (Icon) iconMap.get(v);
478 
479 			if (icon != null && icon instanceof ImageIcon) {
480 
481 				Image image = ((ImageIcon) icon).getImage();
482 
483 				Shape shape = shapeMap.get(image);
484 				if (shape == null) {
485 					if (shapeImages) {
486 						shape = FourPassImageShaper.getShape(image, 30);
487 					} else {
488 						shape = new Rectangle2D.Float(0, 0, 
489 								image.getWidth(null), image.getHeight(null));
490 					}
491                     if(shape.getBounds().getWidth() > 0 && 
492                             shape.getBounds().getHeight() > 0) {
493                         int width = image.getWidth(null);
494                         int height = image.getHeight(null);
495                         AffineTransform transform = 
496                             AffineTransform.getTranslateInstance(-width / 2, -height / 2);
497                         shape = transform.createTransformedShape(shape);
498                         shapeMap.put(image, shape);
499                     }
500 				}
501 				return shape;
502 			} else {
503 				return delegate.transform(v);
504 			}
505 		}
506     }
507     
508     /**
509      * a special renderer that can turn outlines on and off
510      * in this demo.
511      * You won't need this for a real application.
512      * Use BasicVertexRenderer instead
513      * 
514      * @author Tom Nelson
515      *
516      */
517     class DemoRenderer<V,E> extends BasicVertexRenderer<V,E> {
518         public void paintIconForVertex(RenderContext<V,E> rc, V v, Layout<V,E> layout) {
519         	
520             Point2D p = layout.transform(v);
521             p = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, p);
522             float x = (float)p.getX();
523             float y = (float)p.getY();
524 
525             GraphicsDecorator g = rc.getGraphicsContext();
526             boolean outlineImages = false;
527             Transformer<V,Icon> vertexIconFunction = rc.getVertexIconTransformer();
528             
529             if(vertexIconFunction instanceof DemoVertexIconTransformer) {
530                 outlineImages = ((DemoVertexIconTransformer<V>)vertexIconFunction).isOutlineImages();
531             }
532             Icon icon = vertexIconFunction.transform(v);
533             if(icon == null || outlineImages) {
534                 
535                 Shape s = AffineTransform.getTranslateInstance(x,y).
536                     createTransformedShape(rc.getVertexShapeTransformer().transform(v));
537                 paintShapeForVertex(rc, v, s);
538             }
539             if(icon != null) {
540                 int xLoc = (int) (x - icon.getIconWidth()/2);
541                 int yLoc = (int) (y - icon.getIconHeight()/2);
542                 icon.paintIcon(rc.getScreenDevice(), g.getDelegate(), xLoc, yLoc);
543             }
544         }
545     }
546     
547     /**
548 	 * a driver for this demo
549 	 */
550     public static void main(String[] args) {
551         JFrame frame = new JFrame();
552         Container content = frame.getContentPane();
553         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
554 
555         content.add(new VertexImageShaperDemo());
556         frame.pack();
557         frame.setVisible(true);
558     }
559 }