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