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.visualization;
11  
12  import java.awt.Color;
13  import java.awt.Dimension;
14  import java.awt.Graphics;
15  import java.awt.Graphics2D;
16  import java.awt.RenderingHints;
17  import java.awt.RenderingHints.Key;
18  import java.awt.event.ComponentAdapter;
19  import java.awt.event.ComponentEvent;
20  import java.awt.event.ItemEvent;
21  import java.awt.event.ItemListener;
22  import java.awt.geom.AffineTransform;
23  import java.awt.geom.Point2D;
24  import java.awt.image.BufferedImage;
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  import javax.swing.JPanel;
31  import javax.swing.event.ChangeEvent;
32  import javax.swing.event.ChangeListener;
33  
34  import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
35  import edu.uci.ics.jung.algorithms.layout.Layout;
36  import edu.uci.ics.jung.visualization.control.ScalingControl;
37  import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
38  import edu.uci.ics.jung.visualization.decorators.PickableVertexPaintTransformer;
39  import edu.uci.ics.jung.visualization.picking.MultiPickedState;
40  import edu.uci.ics.jung.visualization.picking.PickedState;
41  import edu.uci.ics.jung.visualization.picking.ShapePickSupport;
42  import edu.uci.ics.jung.visualization.renderers.BasicRenderer;
43  import edu.uci.ics.jung.visualization.renderers.Renderer;
44  import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
45  import edu.uci.ics.jung.visualization.util.Caching;
46  import edu.uci.ics.jung.visualization.util.ChangeEventSupport;
47  import edu.uci.ics.jung.visualization.util.DefaultChangeEventSupport;
48  
49  /**
50   * A class that maintains many of the details necessary for creating 
51   * visualizations of graphs.
52   * This is the old VisualizationViewer without tooltips and mouse behaviors. Its purpose is
53   * to be a base class that can also be used on the server side of a multi-tiered application.
54   * 
55   * @author Joshua O'Madadhain
56   * @author Tom Nelson
57   * @author Danyel Fisher
58   */
59  @SuppressWarnings("serial")
60  public class BasicVisualizationServer<V, E> extends JPanel 
61                  implements ChangeListener, ChangeEventSupport, VisualizationServer<V, E>{
62  
63      protected ChangeEventSupport changeSupport =
64          new DefaultChangeEventSupport(this);
65      
66      /**
67       * holds the state of this View
68       */
69      protected VisualizationModel<V,E> model;
70  
71  	/**
72  	 * handles the actual drawing of graph elements
73  	 */
74  	protected Renderer<V,E> renderer = new BasicRenderer<V,E>();
75  	
76  	/**
77  	 * rendering hints used in drawing. Anti-aliasing is on
78  	 * by default
79  	 */
80  	protected Map<Key, Object> renderingHints = new HashMap<Key, Object>();
81  		
82  	/**
83  	 * holds the state of which vertices of the graph are
84  	 * currently 'picked'
85  	 */
86  	protected PickedState<V> pickedVertexState;
87  	
88  	/**
89  	 * holds the state of which edges of the graph are
90  	 * currently 'picked'
91  	 */
92      protected PickedState<E> pickedEdgeState;
93      
94      /**
95       * a listener used to cause pick events to result in
96       * repaints, even if they come from another view
97       */
98      protected ItemListener pickEventListener;
99  	
100 	/**
101 	 * an offscreen image to render the graph
102 	 * Used if doubleBuffered is set to true
103 	 */
104 	protected BufferedImage offscreen;
105 	
106 	/**
107 	 * graphics context for the offscreen image
108 	 * Used if doubleBuffered is set to true
109 	 */
110 	protected Graphics2D offscreenG2d;
111 	
112 	/**
113 	 * user-settable choice to use the offscreen image
114 	 * or not. 'false' by default
115 	 */
116 	protected boolean doubleBuffered;
117 	
118 	/**
119 	 * a collection of user-implementable functions to render under
120 	 * the topology (before the graph is rendered)
121 	 */
122 	protected List<Paintable> preRenderers = new ArrayList<Paintable>();
123 	
124 	/**
125 	 * a collection of user-implementable functions to render over the
126 	 * topology (after the graph is rendered)
127 	 */
128 	protected List<Paintable> postRenderers = new ArrayList<Paintable>();
129 	
130     protected RenderContext<V,E> renderContext;
131     
132     /**
133      * Create an instance with the specified Layout.
134      * 
135      * @param layout		The Layout to apply, with its associated Graph
136      */
137 	public BasicVisualizationServer(Layout<V,E> layout) {
138 	    this(new DefaultVisualizationModel<V,E>(layout));
139 	}
140 	
141     /**
142      * Create an instance with the specified Layout and view dimension.
143      * 
144      * @param layout		The Layout to apply, with its associated Graph
145      * @param preferredSize the preferred size of this View
146      */
147 	public BasicVisualizationServer(Layout<V,E> layout, Dimension preferredSize) {
148 	    this(new DefaultVisualizationModel<V,E>(layout, preferredSize), preferredSize);
149 	}
150 	
151 	/**
152 	 * Create an instance with the specified model and a default dimension (600x600).
153 	 * 
154 	 * @param model the model to use
155 	 */
156 	public BasicVisualizationServer(VisualizationModel<V,E> model) {
157 	    this(model, new Dimension(600,600));
158 	}
159 	
160 	/**
161 	 * Create an instance with the specified model and view dimension.
162 	 * 
163 	 * @param model the model to use
164 	 * @param preferredSize initial preferred size of the view
165 	 */
166     public BasicVisualizationServer(VisualizationModel<V,E> model,
167 	        Dimension preferredSize) {
168 	    this.model = model;
169 	    renderContext = new PluggableRenderContext<V,E>(model.getGraphLayout().getGraph());
170 	    model.addChangeListener(this);
171 	    setDoubleBuffered(false);
172 		this.addComponentListener(new VisualizationListener(this));
173 
174 		setPickSupport(new ShapePickSupport<V,E>(this));
175 		setPickedVertexState(new MultiPickedState<V>());
176 		setPickedEdgeState(new MultiPickedState<E>());
177         
178         renderContext.setEdgeDrawPaintTransformer(new PickableEdgePaintTransformer<E>(getPickedEdgeState(), Color.black, Color.cyan));
179         renderContext.setVertexFillPaintTransformer(new PickableVertexPaintTransformer<V>(getPickedVertexState(), 
180                 Color.red, Color.yellow));
181 		
182 		setPreferredSize(preferredSize);
183 		renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
184 
185         renderContext.getMultiLayerTransformer().addChangeListener(this);
186 	}
187 	
188 	@Override
189     public void setDoubleBuffered(boolean doubleBuffered) {
190 	    this.doubleBuffered = doubleBuffered;
191 	}
192 	
193 	@Override
194     public boolean isDoubleBuffered() {
195 	    return doubleBuffered;
196 	}
197 	
198 	/**
199 	 * Always sanity-check getSize so that we don't use a
200 	 * value that is improbable
201 	 * @see java.awt.Component#getSize()
202 	 */
203 	@Override
204 	public Dimension getSize() {
205 		Dimension d = super.getSize();
206 		if(d.width <= 0 || d.height <= 0) {
207 			d = getPreferredSize();
208 		}
209 		return d;
210 	}
211 
212 	/**
213 	 * Ensure that, if doubleBuffering is enabled, the offscreen
214 	 * image buffer exists and is the correct size.
215 	 * @param d the expected Dimension of the offscreen buffer
216 	 */
217 	protected void checkOffscreenImage(Dimension d) {
218 	    if(doubleBuffered) {
219 	        if(offscreen == null || offscreen.getWidth() != d.width || offscreen.getHeight() != d.height) {
220 	            offscreen = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB);
221 	            offscreenG2d = offscreen.createGraphics();
222 	        }
223 	    }
224 	}
225 	
226     public VisualizationModel<V,E> getModel() {
227         return model;
228     }
229 
230     public void setModel(VisualizationModel<V,E> model) {
231         this.model = model;
232     }
233 
234 	public void stateChanged(ChangeEvent e) {
235 	    repaint();
236 	    fireStateChanged();
237 	}
238 
239 	public void setRenderer(Renderer<V,E> r) {
240 	    this.renderer = r;
241 	    repaint();
242 	}
243 	
244 	public Renderer<V,E> getRenderer() {
245 	    return renderer;
246 	}
247 
248     public void setGraphLayout(Layout<V,E> layout) {
249     	Dimension viewSize = getPreferredSize();
250     	if(this.isShowing()) {
251     		viewSize = getSize();
252     	}
253 	    model.setGraphLayout(layout, viewSize);
254     }
255     
256     public void scaleToLayout(ScalingControl scaler) {
257     	Dimension vd = getPreferredSize();
258     	if(this.isShowing()) {
259     		vd = getSize();
260     	}
261 		Dimension ld = getGraphLayout().getSize();
262 		if(vd.equals(ld) == false) {
263 			scaler.scale(this, (float)(vd.getWidth()/ld.getWidth()), new Point2D.Double());
264 		}
265     }
266 	
267 	public Layout<V,E> getGraphLayout() {
268 	        return model.getGraphLayout();
269 	}
270 	
271 	@Override
272     public void setVisible(boolean aFlag) {
273 		super.setVisible(aFlag);
274 		if(aFlag == true) {
275 			Dimension d = this.getSize();
276 			if(d.width <= 0 || d.height <= 0) {
277 				d = this.getPreferredSize();
278 			}
279 			model.getGraphLayout().setSize(d);
280 		}
281 	}
282 
283     public Map<Key, Object> getRenderingHints() {
284         return renderingHints;
285     }
286     
287     public void setRenderingHints(Map<Key, Object> renderingHints) {
288         this.renderingHints = renderingHints;
289     }
290     
291 	@Override
292     protected void paintComponent(Graphics g) {
293         super.paintComponent(g);
294 
295 		Graphics2D g2d = (Graphics2D)g;
296 		if(doubleBuffered) {
297 		    checkOffscreenImage(getSize());
298 			renderGraph(offscreenG2d);
299 		    g2d.drawImage(offscreen, null, 0, 0);
300 		} else {
301 		    renderGraph(g2d);
302 		}
303 	}
304 	
305 	protected void renderGraph(Graphics2D g2d) {
306 	    if(renderContext.getGraphicsContext() == null) {
307 	        renderContext.setGraphicsContext(new GraphicsDecorator(g2d));
308         } else {
309         	renderContext.getGraphicsContext().setDelegate(g2d);
310         }
311         renderContext.setScreenDevice(this);
312 	    Layout<V,E> layout = model.getGraphLayout();
313 
314 		g2d.setRenderingHints(renderingHints);
315 		
316 		// the size of the VisualizationViewer
317 		Dimension d = getSize();
318 		
319 		// clear the offscreen image
320 		g2d.setColor(getBackground());
321 		g2d.fillRect(0,0,d.width,d.height);
322 
323 		AffineTransform oldXform = g2d.getTransform();
324         AffineTransform newXform = new AffineTransform(oldXform);
325         newXform.concatenate(
326         		renderContext.getMultiLayerTransformer().getTransformer(Layer.VIEW).getTransform());
327 		
328         g2d.setTransform(newXform);
329 
330 		// if there are  preRenderers set, paint them
331 		for(Paintable paintable : preRenderers) {
332 
333 		    if(paintable.useTransform()) {
334 		        paintable.paint(g2d);
335 		    } else {
336 		        g2d.setTransform(oldXform);
337 		        paintable.paint(g2d);
338                 g2d.setTransform(newXform);
339 		    }
340 		}
341 		
342         if(layout instanceof Caching) {
343         	((Caching)layout).clear();
344         }
345         
346         renderer.render(renderContext, layout);
347 		
348 		// if there are postRenderers set, do it
349 		for(Paintable paintable : postRenderers) {
350 
351 		    if(paintable.useTransform()) {
352 		        paintable.paint(g2d);
353 		    } else {
354 		        g2d.setTransform(oldXform);
355 		        paintable.paint(g2d);
356                 g2d.setTransform(newXform);
357 		    }
358 		}
359 		g2d.setTransform(oldXform);
360 	}
361 
362 	/**
363 	 * VisualizationListener reacts to changes in the size of the
364 	 * VisualizationViewer. When the size changes, it ensures
365 	 * that the offscreen image is sized properly. 
366 	 * If the layout is locked to this view size, then the layout
367 	 * is also resized to be the same as the view size.
368 	 *
369 	 *
370 	 */
371 	protected class VisualizationListener extends ComponentAdapter {
372 		protected BasicVisualizationServer<V,E> vv;
373 		public VisualizationListener(BasicVisualizationServer<V,E> vv) {
374 			this.vv = vv;
375 		}
376 
377 		/**
378 		 * create a new offscreen image for the graph
379 		 * whenever the window is resied
380 		 */
381 		@Override
382         public void componentResized(ComponentEvent e) {
383 		    Dimension d = vv.getSize();
384 		    if(d.width <= 0 || d.height <= 0) return;
385 		    checkOffscreenImage(d);
386 		    repaint();
387 		}
388 	}
389 
390     public void addPreRenderPaintable(Paintable paintable) {
391         if(preRenderers == null) {
392             preRenderers = new ArrayList<Paintable>();
393         }
394         preRenderers.add(paintable);
395     }
396     
397     public void prependPreRenderPaintable(Paintable paintable) {
398         if(preRenderers == null) {
399             preRenderers = new ArrayList<Paintable>();
400         }
401         preRenderers.add(0,paintable);
402     }
403     
404     public void removePreRenderPaintable(Paintable paintable) {
405         if(preRenderers != null) {
406             preRenderers.remove(paintable);
407         }
408     }
409     
410     public void addPostRenderPaintable(Paintable paintable) {
411         if(postRenderers == null) {
412             postRenderers = new ArrayList<Paintable>();
413         }
414         postRenderers.add(paintable);
415     }
416     
417     public void prependPostRenderPaintable(Paintable paintable) {
418         if(postRenderers == null) {
419             postRenderers = new ArrayList<Paintable>();
420         }
421         postRenderers.add(0,paintable);
422     }
423     
424     public void removePostRenderPaintable(Paintable paintable) {
425         if(postRenderers != null) {
426             postRenderers.remove(paintable);
427         }
428     }
429 
430     public void addChangeListener(ChangeListener l) {
431         changeSupport.addChangeListener(l);
432     }
433     
434     public void removeChangeListener(ChangeListener l) {
435         changeSupport.removeChangeListener(l);
436     }
437     
438     public ChangeListener[] getChangeListeners() {
439         return changeSupport.getChangeListeners();
440     }
441 
442     public void fireStateChanged() {
443         changeSupport.fireStateChanged();
444     }   
445     
446     public PickedState<V> getPickedVertexState() {
447         return pickedVertexState;
448     }
449 
450     public PickedState<E> getPickedEdgeState() {
451         return pickedEdgeState;
452     }
453     
454     public void setPickedVertexState(PickedState<V> pickedVertexState) {
455         if(pickEventListener != null && this.pickedVertexState != null) {
456             this.pickedVertexState.removeItemListener(pickEventListener);
457         }
458         this.pickedVertexState = pickedVertexState;
459         this.renderContext.setPickedVertexState(pickedVertexState);
460         if(pickEventListener == null) {
461             pickEventListener = new ItemListener() {
462 
463                 public void itemStateChanged(ItemEvent e) {
464                     repaint();
465                 }
466             };
467         }
468         pickedVertexState.addItemListener(pickEventListener);
469     }
470     
471     public void setPickedEdgeState(PickedState<E> pickedEdgeState) {
472         if(pickEventListener != null && this.pickedEdgeState != null) {
473             this.pickedEdgeState.removeItemListener(pickEventListener);
474         }
475         this.pickedEdgeState = pickedEdgeState;
476         this.renderContext.setPickedEdgeState(pickedEdgeState);
477         if(pickEventListener == null) {
478             pickEventListener = new ItemListener() {
479 
480                 public void itemStateChanged(ItemEvent e) {
481                     repaint();
482                 }
483             };
484         }
485         pickedEdgeState.addItemListener(pickEventListener);
486     }
487     
488     public GraphElementAccessor<V,E> getPickSupport() {
489         return renderContext.getPickSupport();
490     }
491 
492     public void setPickSupport(GraphElementAccessor<V,E> pickSupport) {
493         renderContext.setPickSupport(pickSupport);
494     }
495     
496     public Point2D getCenter() {
497         Dimension d = getSize();
498         return new Point2D.Float(d.width/2, d.height/2);
499     }
500 
501     public RenderContext<V,E> getRenderContext() {
502         return renderContext;
503     }
504 
505     public void setRenderContext(RenderContext<V,E> renderContext) {
506         this.renderContext = renderContext;
507     }
508 }