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.event.ActionEvent;
20  import java.awt.event.ActionListener;
21  import java.awt.event.ItemEvent;
22  import java.awt.event.ItemListener;
23  import java.util.HashMap;
24  import java.util.Map;
25  
26  import javax.media.jai.PerspectiveTransform;
27  import javax.swing.BorderFactory;
28  import javax.swing.ButtonGroup;
29  import javax.swing.Icon;
30  import javax.swing.ImageIcon;
31  import javax.swing.JApplet;
32  import javax.swing.JButton;
33  import javax.swing.JCheckBox;
34  import javax.swing.JFrame;
35  import javax.swing.JMenu;
36  import javax.swing.JMenuBar;
37  import javax.swing.JPanel;
38  import javax.swing.JRadioButton;
39  import javax.swing.JSlider;
40  import javax.swing.event.ChangeEvent;
41  import javax.swing.event.ChangeListener;
42  
43  import org.apache.commons.collections15.Transformer;
44  
45  import edu.uci.ics.jung.algorithms.layout.FRLayout;
46  import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
47  import edu.uci.ics.jung.graph.Graph;
48  import edu.uci.ics.jung.graph.util.EdgeType;
49  import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
50  import edu.uci.ics.jung.visualization.LayeredIcon;
51  import edu.uci.ics.jung.visualization.VisualizationServer;
52  import edu.uci.ics.jung.visualization.VisualizationViewer;
53  import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
54  import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
55  import edu.uci.ics.jung.visualization.control.ScalingControl;
56  import edu.uci.ics.jung.visualization.decorators.DefaultVertexIconTransformer;
57  import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer;
58  import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
59  import edu.uci.ics.jung.visualization.decorators.VertexIconShapeTransformer;
60  import edu.uci.ics.jung.visualization.jai.PerspectiveImageLensSupport;
61  import edu.uci.ics.jung.visualization.jai.PerspectiveLayoutTransformSupport;
62  import edu.uci.ics.jung.visualization.jai.PerspectiveTransformSupport;
63  import edu.uci.ics.jung.visualization.picking.PickedState;
64  import edu.uci.ics.jung.visualization.renderers.Checkmark;
65  
66  
67  /**
68   * Demonstrates the use of images to represent graph vertices.
69   * The images are added to the DefaultGraphLabelRenderer and can
70   * either be offset from the vertex, or centered on the vertex.
71   * Additionally, the relative positioning of the label and
72   * image is controlled by subclassing the DefaultGraphLabelRenderer
73   * and setting the appropriate properties on its JLabel superclass
74   *  FancyGraphLabelRenderer
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 PerspectiveVertexImageShaperDemo extends JApplet {
88  
89      /**
90  	 * 
91  	 */
92  	private static final long serialVersionUID = -1943329822820885759L;
93  
94  	/**
95       * the graph
96       */
97      Graph<Number,Number> graph;
98  
99      /**
100      * the visual component and renderer for the graph
101      */
102     VisualizationViewer<Number,Number> vv;
103     
104     /**
105      * some icon names to use
106      */
107     String[] iconNames = {
108             "apple",
109             "os",
110             "x",
111             "linux",
112             "inputdevices",
113             "wireless",
114             "graphics3",
115             "gamespcgames",
116             "humor",
117             "music",
118             "privacy"
119     };
120     
121     PerspectiveTransformSupport viewSupport;
122     PerspectiveTransformSupport layoutSupport;
123     /**
124      * create an instance of a simple graph with controls to
125      * demo the zoom features.
126      * 
127      */
128     @SuppressWarnings("serial")
129 	public PerspectiveVertexImageShaperDemo() {
130         
131         // create a simple graph for the demo
132         graph = new DirectedSparseMultigraph<Number,Number>();
133         Number[] vertices = createVertices(11);
134         
135         // a Map for the labels
136         Map<Number,String> map = new HashMap<Number,String>();
137         for(int i=0; i<vertices.length; i++) {
138             map.put(vertices[i], iconNames[i%iconNames.length]);
139         }
140         
141         // a Map for the Icons
142         Map<Number,Icon> iconMap = new HashMap<Number,Icon>();
143         for(int i=0; i<vertices.length; i++) {
144             String name = "/images/topic"+iconNames[i]+".gif";
145             try {
146                 Icon icon = 
147                     new LayeredIcon(new ImageIcon(PerspectiveVertexImageShaperDemo.class.getResource(name)).getImage());
148                 iconMap.put(vertices[i], icon);
149             } catch(Exception ex) {
150                 System.err.println("You need slashdoticons.jar in your classpath to see the image "+name);
151             }
152         }
153         
154         createEdges(vertices);
155         
156         final VertexStringerImpl<Number> vertexStringerImpl = 
157             new VertexStringerImpl<Number>(map);
158         
159         final VertexIconShapeTransformer<Number> vertexImageShapeFunction =
160             new VertexIconShapeTransformer<Number>(new EllipseVertexShapeTransformer<Number>());
161         
162         FRLayout<Number,Number> layout = new FRLayout<Number,Number>(graph);
163         layout.setMaxIterations(100);
164         vv =  new VisualizationViewer<Number,Number>(layout, new Dimension(400,400));
165 
166         vv.setBackground(Color.decode("0xffffdd"));
167         final DefaultVertexIconTransformer<Number> vertexIconFunction =
168         	new DefaultVertexIconTransformer<Number>();
169         vertexImageShapeFunction.setIconMap(iconMap);
170         vertexIconFunction.setIconMap(iconMap);
171         vv.getRenderContext().setVertexShapeTransformer(vertexImageShapeFunction);
172         vv.getRenderContext().setVertexIconTransformer(vertexIconFunction);
173         vv.getRenderContext().setVertexLabelTransformer(vertexStringerImpl);
174         PickedState<Number> ps = vv.getPickedVertexState();
175         ps.addItemListener(new PickWithIconListener(vertexIconFunction));
176 
177 
178         vv.addPostRenderPaintable(new VisualizationServer.Paintable(){
179             int x;
180             int y;
181             Font font;
182             FontMetrics metrics;
183             int swidth;
184             int sheight;
185             String str = "Thank You, slashdot.org, for the images!";
186             
187             public void paint(Graphics g) {
188                 Dimension d = vv.getSize();
189                 if(font == null) {
190                     font = new Font(g.getFont().getName(), Font.BOLD, 20);
191                     metrics = g.getFontMetrics(font);
192                     swidth = metrics.stringWidth(str);
193                     sheight = metrics.getMaxAscent()+metrics.getMaxDescent();
194                     x = (d.width-swidth)/2;
195                     y = (int)(d.height-sheight*1.5);
196                 }
197                 g.setFont(font);
198                 Color oldColor = g.getColor();
199                 g.setColor(Color.lightGray);
200                 g.drawString(str, x, y);
201                 g.setColor(oldColor);
202             }
203             public boolean useTransform() {
204                 return false;
205             }
206         });
207 
208         // add a listener for ToolTips
209         vv.setVertexToolTipTransformer(new ToStringLabeller<Number>());
210         
211         Container content = getContentPane();
212         final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
213         content.add(panel);
214         
215         final DefaultModalGraphMouse<Number, Number> graphMouse = 
216             new DefaultModalGraphMouse<Number, Number>();
217 
218         vv.setGraphMouse(graphMouse);
219         
220         this.viewSupport = new PerspectiveImageLensSupport<Number,Number>(vv);
221         this.layoutSupport = new PerspectiveLayoutTransformSupport<Number,Number>(vv);
222         
223         final ScalingControl scaler = new CrossoverScalingControl();
224 
225         JButton plus = new JButton("+");
226         plus.addActionListener(new ActionListener() {
227             public void actionPerformed(ActionEvent e) {
228                 scaler.scale(vv, 1.1f, vv.getCenter());
229             }
230         });
231         JButton minus = new JButton("-");
232         minus.addActionListener(new ActionListener() {
233             public void actionPerformed(ActionEvent e) {
234                 scaler.scale(vv, 0.9f, vv.getCenter());
235             }
236         });
237         final JSlider horizontalSlider = new JSlider(-120,120,0){
238 
239 			/* (non-Javadoc)
240 			 * @see javax.swing.JComponent#getPreferredSize()
241 			 */
242 			@Override
243 			public Dimension getPreferredSize() {
244 				return new Dimension(80, super.getPreferredSize().height);
245 			}
246         };
247         
248         final JSlider verticalSlider = new JSlider(-120,120,0) {
249 
250 			/* (non-Javadoc)
251 			 * @see javax.swing.JComponent#getPreferredSize()
252 			 */
253 			@Override
254 			public Dimension getPreferredSize() {
255 				return new Dimension(super.getPreferredSize().width, 80);
256 			}
257         };
258         verticalSlider.setOrientation(JSlider.VERTICAL);
259         final ChangeListener changeListener = new ChangeListener() {
260 
261 			public void stateChanged(ChangeEvent e) {
262                 int vval = -verticalSlider.getValue();
263                 int hval = horizontalSlider.getValue();
264 
265                 Dimension d = vv.getSize();
266                  PerspectiveTransform pt = null;
267                     pt = PerspectiveTransform.getQuadToQuad(
268                             vval,          hval, 
269                             d.width-vval, -hval, 
270                             d.width+vval, d.height+hval, 
271                             -vval,         d.height-hval,
272                             
273                             0, 0, 
274                             d.width, 0, 
275                             d.width, d.height, 
276                             0, d.height);
277 
278                 viewSupport.getPerspectiveTransformer().setPerspectiveTransform(pt);
279                 layoutSupport.getPerspectiveTransformer().setPerspectiveTransform(pt);
280                 vv.repaint();
281 			}};
282 		horizontalSlider.addChangeListener(changeListener);
283 		verticalSlider.addChangeListener(changeListener);
284 		
285 		
286         JPanel perspectivePanel = new JPanel(new BorderLayout());
287         JPanel perspectiveCenterPanel = new JPanel(new BorderLayout());
288         perspectivePanel.setBorder(BorderFactory.createTitledBorder("Perspective Controls"));
289         final JButton center = new JButton("Center");
290         center.addActionListener(new ActionListener() {
291 
292 			public void actionPerformed(ActionEvent e) {
293 				horizontalSlider.setValue(0);
294 				verticalSlider.setValue(0);
295 			}});
296 
297         final JCheckBox noText = new JCheckBox("No Text");
298         noText.addItemListener(new ItemListener(){
299 
300             public void itemStateChanged(ItemEvent e) {
301                 JCheckBox cb = (JCheckBox)e.getSource();
302                 vertexStringerImpl.setEnabled(!cb.isSelected());
303                 vv.repaint();
304             }
305         });
306         JPanel centerPanel = new JPanel();
307         centerPanel.add(noText);
308 
309         ButtonGroup radio = new ButtonGroup();
310         JRadioButton none = new JRadioButton("None");
311         none.addItemListener(new ItemListener(){
312             public void itemStateChanged(ItemEvent e) {
313             	boolean selected = e.getStateChange() == ItemEvent.SELECTED;
314                 if(selected) {
315                     if(viewSupport != null) {
316                         viewSupport.deactivate();
317                     }
318                     if(layoutSupport != null) {
319                         layoutSupport.deactivate();
320                     }
321                 }
322                 center.setEnabled(!selected);
323                 horizontalSlider.setEnabled(!selected);
324                 verticalSlider.setEnabled(!selected);
325             }
326         });
327         none.setSelected(true);
328 
329         JRadioButton hyperView = new JRadioButton("View");
330         hyperView.addItemListener(new ItemListener(){
331             public void itemStateChanged(ItemEvent e) {
332                 viewSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
333             }
334         });
335 
336         JRadioButton hyperModel = new JRadioButton("Layout");
337         hyperModel.addItemListener(new ItemListener(){
338             public void itemStateChanged(ItemEvent e) {
339                 layoutSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
340             }
341         });
342         radio.add(none);
343         radio.add(hyperView);
344         radio.add(hyperModel);
345         
346         JMenuBar menubar = new JMenuBar();
347         JMenu modeMenu = graphMouse.getModeMenu();
348         menubar.add(modeMenu);
349 
350         panel.setCorner(menubar);
351         
352         JPanel scaleGrid = new JPanel(new GridLayout(2,0));
353         scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom"));
354         JPanel controls = new JPanel(new BorderLayout());
355         scaleGrid.add(plus);
356         scaleGrid.add(minus);
357         controls.add(scaleGrid, BorderLayout.WEST);
358 
359         JPanel lensPanel = new JPanel(new GridLayout(2,0));
360         lensPanel.add(none);
361         lensPanel.add(hyperView);
362         lensPanel.add(hyperModel);
363 
364         perspectivePanel.add(lensPanel, BorderLayout.WEST);
365         perspectiveCenterPanel.add(horizontalSlider, BorderLayout.SOUTH);
366         perspectivePanel.add(verticalSlider, BorderLayout.EAST);
367         perspectiveCenterPanel.add(center);
368         perspectivePanel.add(perspectiveCenterPanel);
369         controls.add(perspectivePanel, BorderLayout.EAST);
370         
371         
372         controls.add(centerPanel);
373         content.add(controls, BorderLayout.SOUTH);
374     }
375     
376     /**
377      * A simple implementation of VertexStringer that
378      * gets Vertex labels from a Map  
379      * 
380      * @author Tom Nelson 
381      *
382      *
383      */
384     class VertexStringerImpl<V> implements Transformer<V,String> {
385 
386         Map<V,String> map = new HashMap<V,String>();
387         
388         boolean enabled = true;
389         
390         public VertexStringerImpl(Map<V,String> map) {
391             this.map = map;
392         }
393         
394         public String transform(Object v) {
395             if(isEnabled()) {
396                 return map.get(v);
397             } else {
398                 return "";
399             }
400         }
401 
402         /**
403          * @return Returns the enabled.
404          */
405         public boolean isEnabled() {
406             return enabled;
407         }
408 
409         /**
410          * @param enabled The enabled to set.
411          */
412         public void setEnabled(boolean enabled) {
413             this.enabled = enabled;
414         }
415     }
416     
417     /**
418      * create some vertices
419      * @param count how many to create
420      * @return the Vertices in an array
421      */
422     private Number[] createVertices(int count) {
423         Number[] v = new Number[count];
424         for (int i = 0; i < count; i++) {
425             v[i] = new Integer(i);
426             graph.addVertex(v[i]);
427         }
428         return v;
429     }
430 
431     /**
432      * create edges for this demo graph
433      * @param v an array of Vertices to connect
434      */
435     void createEdges(Number[] v) {
436         graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED);
437         graph.addEdge(new Double(Math.random()), v[3], v[0], EdgeType.DIRECTED);
438         graph.addEdge(new Double(Math.random()), v[0], v[4], EdgeType.DIRECTED);
439         graph.addEdge(new Double(Math.random()), v[4], v[5], EdgeType.DIRECTED);
440         graph.addEdge(new Double(Math.random()), v[5], v[3], EdgeType.DIRECTED);
441         graph.addEdge(new Double(Math.random()), v[2], v[1], EdgeType.DIRECTED);
442         graph.addEdge(new Double(Math.random()), v[4], v[1], EdgeType.DIRECTED);
443         graph.addEdge(new Double(Math.random()), v[8], v[2], EdgeType.DIRECTED);
444         graph.addEdge(new Double(Math.random()), v[3], v[8], EdgeType.DIRECTED);
445         graph.addEdge(new Double(Math.random()), v[6], v[7], EdgeType.DIRECTED);
446         graph.addEdge(new Double(Math.random()), v[7], v[5], EdgeType.DIRECTED);
447         graph.addEdge(new Double(Math.random()), v[0], v[9], EdgeType.DIRECTED);
448         graph.addEdge(new Double(Math.random()), v[9], v[8], EdgeType.DIRECTED);
449         graph.addEdge(new Double(Math.random()), v[7], v[6], EdgeType.DIRECTED);
450         graph.addEdge(new Double(Math.random()), v[6], v[5], EdgeType.DIRECTED);
451         graph.addEdge(new Double(Math.random()), v[4], v[2], EdgeType.DIRECTED);
452         graph.addEdge(new Double(Math.random()), v[5], v[4], EdgeType.DIRECTED);
453         graph.addEdge(new Double(Math.random()), v[4], v[10], EdgeType.DIRECTED);
454         graph.addEdge(new Double(Math.random()), v[10], v[4], EdgeType.DIRECTED);
455     }
456     
457     public static class PickWithIconListener implements ItemListener {
458         DefaultVertexIconTransformer<Number> imager;
459         Icon checked;
460         
461         public PickWithIconListener(DefaultVertexIconTransformer<Number> imager) {
462             this.imager = imager;
463             checked = new Checkmark(Color.red);
464         }
465 
466         public void itemStateChanged(ItemEvent e) {
467             Icon icon = imager.transform((Number)e.getItem());
468             if(icon != null && icon instanceof LayeredIcon) {
469                 if(e.getStateChange() == ItemEvent.SELECTED) {
470                     ((LayeredIcon)icon).add(checked);
471                 } else {
472                     ((LayeredIcon)icon).remove(checked);
473                 }
474             }
475         }
476     }
477 
478     /**
479      * a simple Icon that draws a checkmark in the lower-right quadrant of its
480      * area. Used to draw a checkmark on Picked Vertices.
481      */
482 //    public static class Checkmark implements Icon {
483 //
484 //            GeneralPath path = new GeneralPath();
485 //            AffineTransform highlight = AffineTransform.getTranslateInstance(-1,-1);
486 //            AffineTransform lowlight = AffineTransform.getTranslateInstance(1,1);
487 //            AffineTransform shadow = AffineTransform.getTranslateInstance(2,2);
488 //            Color color;
489 //            public Checkmark() {
490 //                this(Color.red);
491 //            }
492 //            public Checkmark(Color color) {
493 //                this.color = color;
494 //                path.moveTo(10,17);
495 //                path.lineTo(13,20);
496 //                path.lineTo(20,13);
497 //            }
498 //        public void paintIcon(Component c, Graphics g, int x, int y) {
499 //            Shape shape = AffineTransform.getTranslateInstance(x, y).createTransformedShape(path);
500 //            Graphics2D g2d = (Graphics2D)g;
501 //            g2d.addRenderingHints(Collections.singletonMap(RenderingHints.KEY_ANTIALIASING, 
502 //                    RenderingHints.VALUE_ANTIALIAS_ON));
503 //            g2d.setStroke(new BasicStroke(4));
504 //            g2d.setColor(Color.darkGray);
505 //            g2d.draw(shadow.createTransformedShape(shape));
506 //            g2d.setColor(Color.black);
507 //            g2d.draw(lowlight.createTransformedShape(shape));
508 //            g2d.setColor(Color.white);
509 //            g2d.draw(highlight.createTransformedShape(shape));
510 //            g2d.setColor(color);
511 //            g2d.draw(shape);
512 //        }
513 //
514 //        public int getIconWidth() {
515 //            return 20;
516 //        }
517 //
518 //        public int getIconHeight() {
519 //            return 20;
520 //        }
521 //    }
522 //   /**
523 //     * An icon that is made up of a collection of Icons.
524 //     * They are rendered in layers starting with the first
525 //     * Icon added (from the constructor).
526 //     * 
527 //     * @author Tom Nelson
528 //     *
529 //     */
530 //    public static class LayeredIcon extends ImageIcon {
531 //
532 //		/**
533 //		 * 
534 //		 */
535 //    	private static final long serialVersionUID = -2975294939874762164L;
536 //		Set<Icon> iconSet = new LinkedHashSet<Icon>();
537 //
538 //		public LayeredIcon(Image image) {
539 //            super(image);
540 //		}
541 //
542 //        public void paintIcon(Component c, Graphics g, int x, int y) {
543 //            super.paintIcon(c, g, x, y);
544 //            Dimension d = new Dimension(getIconWidth(), getIconHeight());
545 //            for (Iterator iterator = iconSet.iterator(); iterator.hasNext();) {
546 //                Icon icon = (Icon) iterator.next();
547 //                 Dimension id = new Dimension(icon.getIconWidth(), icon.getIconHeight());
548 //                 int dx = (d.width - id.width)/2;
549 //                 int dy = (d.height - id.height)/2;
550 //                icon.paintIcon(c, g, x+dx, y+dy);
551 //            }
552 //        }
553 //
554 //		public void add(Icon icon) {
555 //			iconSet.add(icon);
556 //		}
557 //
558 //		public boolean remove(Icon icon) {
559 //			return iconSet.remove(icon);
560 //		}
561 //	}
562 
563     /**
564      * a driver for this demo
565      */
566     public static void main(String[] args) {
567         JFrame frame = new JFrame();
568         Container content = frame.getContentPane();
569         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
570 
571         content.add(new PerspectiveVertexImageShaperDemo());
572         frame.pack();
573         frame.setVisible(true);
574     }
575 }